//
//  WCProject.m
//  WabbitCode
//
//  Created by William Towe on 1/22/09.
//  Copyright 2009 Revolution Software. All rights reserved.
//

#import "WCProject.h"
#import "NSArray+WCExtensions.h"
#import "WCMacros.h"
#import "WCGradientTableHeaderCell.h"
#import "WCProjectWindowController.h"
#import "WCTreeNode.h"
#import "WCProjectGroup.h"
#import "NSOutlineView+WCExtensions.h"
#import "NSTableView+WCExtensions.h"
#import "NSDictionary+WCExtensions.h"
#import "WCAddToProjectSheetController.h"
#import "NSTreeController+WCExtensions.h"
#import "WCBuildTargetsGroup.h"
#import "NSIndexPath+WCExtensions.h"
#import "WCBuildTarget.h"
#import "WCBuildConfiguration.h"
#import "WCIncludeDirectory.h"
#import "WCBuildStep.h"
#import "WCAppController.h"
#import "WCTextView.h"
#import "WCTextFile.h"
#import "WCErrorsAndWarningsGroup.h"
#import "NSTreeNode+WCExtensions.h"
#import "NSTabView+WCExtensions.h"
#import "NSIndexSet+WCExtensions.h"
#import "WCProjectMenuController.h"
#import <RegexKit/RegexKit.h>
#import "WCUnsavedFilesWindowController.h"
#import "WCRulerView.h"
#import "WCInterfacePerformer.h"
#import <PSMTabBarControl/PSMTabBarControl.h>
#import "PSMTabBarControl+WCExtensions.h"
#import "WCProjectBuildStatusTextFieldCell.h"
#import "WCDockBadgeView.h"
#import "WCSyntaxHighlighter.h"
#import "WCBuildMessage.h"
#import "WCBadgedIconAndTextFieldCell.h"
#import "WCFindInProjectWindowController.h"
#import "MGScopeBar.h"
#import "WCBuildResultsWindowController.h"
#import "WCSymbol.h"
#import "WCProjectSplitView.h"
#import "WCBuildTargetGetInfoWindowController.h"
#import "WCBasicPerformer.h"
#import "WCFileGetInfoWindowController.h"
#import "NSAttributedString+WCExtensions.h"
#import "NSTableView+RSAutosaving.h"
#import "WCCodeCountSheetController.h"
#import "NSTextView+WCExtensions.h"
#import "WCBookmarksGroup.h"
#import "WCBookmark.h"
#import "WCCurrentTextFileSymbolsTableView.h"
#import "WCProjectOutlineView.h"
#import "WCKeyDownTableView.h"
#import "WCProjectSymbolsGroup.h"
#import "WCDocumentController.h"
#import "WCBuildProduct.h"
#import "WCBuildProductsGroup.h"
#import "WCTextFileWindowController.h"
#import "WCNewTargetWindowController.h"


// project outline view column identifiers
#define kWCProjectOutlineViewGroupsAndFilesColumnIdentifier @"Groups & Files"

// file detail table view column identifiers
#define kWCProjectFileTableViewIconColumnIdentifier @"Icon"
#define kWCProjectFileTableViewNameColumnIdentifier @"Name"
#define kWCProjectFileDetailTableViewErrorsColumnIdentifier @"Errors"
#define kWCProjectFileDetailTableViewWarningsColumnIdentifier @"Warnings"

// errors and warnings table view column identifiers
#define kWCProjectErrorsAndWarningsTableViewIconColumnIdentifier @"Icon"
#define kWCProjectErrorsAndWarningsTableViewMessageColumnIdentifier @"Message"
#define kWCProjectErrorsAndWarningsTableViewLocationColumnIdentifier @"Location"

// labels table view column identifiers
#define kWCProjectCurrentTextFileSymbolsSymbolNameColumnIdentifier @"Symbol"

// keys for storing the project settings
NSString *kWCProjectPrefsProjectWindowFrameKey = @"projectWindowFrame";
NSString *kWCProjectPrefsProjectOutlineViewExpandedItemIndexPathStringsKey = @"projectOutlineViewExpandedItemIndexPaths";
NSString *kWCProjectPrefsProjectLeftVerticalSplitViewDividerPositionKey = @"projectLeftVerticalSplitViewDividerPosition";
NSString *kWCProjectPrefsProjectRightVerticalSplitViewDividerPositionKey = @"projectRightVerticalSplitViewDividerPosition";
NSString *kWCProjectPrefsProjectHorizontalSplitViewDividerPositionKey = @"projectHorizontalSplitViewDividerPosition";
NSString *kWCProjectPrefsProjectOutlineViewSelectedRowIndexesKey = @"projectOutlineViewSelectedRowIndexes";
NSString *kWCProjectPrefsProjectOpenFileNamesKey = @"projectOpenFileNames";
NSString *kWCProjectPrefsProjectSelectedOpenFileNameKey = @"projectSelectedOpenFileName";

NSString *kWCProjectPrefsProjectFileTableViewSortDescriptorDictionariesKey = @"projectFileTableViewSortDescriptors";
NSString *kWCProjectPrefsProjectBookmarksTableViewSortDescriptorsDictionariesKey = @"projectBookmarksTableViewSortDescriptors";
NSString *kWCProjectPrefsProjectCurrentTextFileSymbolsTableViewSortDescriptorDictionariesKey = @"currentTextFileSymbolsTableViewSortDescriptors";
NSString *kWCProjectPrefsProjectSymbolsTableViewSortDescriptorsDictionariesKey = @"projectSymbolsTableViewSortDescriptors";

NSString *kWCProjectPrefsProjectFileTableViewAutosaveDictionaryKey = @"projectFileTableViewAutosaveDictionary";
NSString *kWCProjectPrefsProjectErrorsAndWarningsTableViewAutosaveDictionaryKey = @"projectErrorsAndWarningsTableViewAutosaveDictionary";
NSString *kWCProjectPrefsProjectBookmarksTableViewAutosaveDictionaryKey = @"projectBookmarksTableViewAutosaveDictionary";
NSString *kWCProjectPrefsProjectSymbolsTableViewAutosaveDictionaryKey = @"projectProjectSymbolsTableViewAutosaveDictionary";

// toolbar identifiers
NSString *kWCProjectToolbarSearchIdentifier = @"kWCProjectToolbarSearchIdentifier";
NSString *kWCProjectToolbarBuildIdentifier = @"kWCProjectToolbarBuildIdentifier";
NSString *kWCProjectToolbarGetInfoIdentifier = @"kWCProjectToolbarGetInfoIdentifier";
NSString *kWCProjectToolbarProjectWindowIdentifier = @"kWCProjectToolbarProjectWindowIdentifier";
NSString *kWCProjectToolbarProjectOverviewIdentifier = @"kWCProjectToolbarProjectOverviewIdentifier";
NSString *kWCProjectToolbarCountCodeIdentifier = @"kWCProjectToolbarCountCodeIdentifier";
NSString *kWCProjectToolbarBuildResultsWindowIdentifier = @"kWCProjectToolbarBuildResultsWindowIdentifier";
NSString *kWCProjectToolbarFindInProjectWindowIdentifier = @"kWCProjectToolbarFindInProjectWindowIdentifier";
NSString *kWCProjectToolbarDebuggerWindowIdentifier = @"kWCProjectToolbarDebuggerWindowIdentifier";
NSString *kWCProjectToolbarBuildAndDebugIdentifier = @"kWCProjectToolbarBuildAndDebugIdentifier";
NSString *kWCProjectToolbarBuildAndRunIdentifier = @"kWCProjectToolbarBuildAndRunIdentifier";

// notifications
NSString *kWCProjectActiveBuildTargetDidChangeNotification = @"kWCProjectActiveBuildTargetDidChangeNotification";
NSString *kWCProjectActiveBuildConfigurationDidChangeNotification = @"kWCProjectActiveBuildConfigurationDidChangeNotification";

static NSFileManager *fm = nil;
static NSWorkspace *sw = nil;

@interface WCProject (Private)
- (void)_loadProjectPreferences;
- (void)_loadDefaultProjectPreferences;
- (void)_updateProjectPreferences;

- (void)_showErrorOrWarningForBuildMessage:(WCBuildMessage *)bm;
- (void)_jumpToBookmark:(WCBookmark *)bookmark;

- (void)_buildNextStep;

- (NSString *)_projectDataPathFromWrapperURL:(NSURL *)url;
- (NSString *)_projectPreferencesPathFromWrapperURL:(NSURL *)url;
- (NSString *)_projectDataPathFromWrapperPath:(NSString *)path;
- (NSString *)_projectPreferencesPathFromWrapperPath:(NSString *)path;
@end

@implementation WCProject
#pragma mark -
#pragma mark *** Subclass Overrides ***
+ (void)initialize {
	fm = [NSFileManager defaultManager];
	sw = [NSWorkspace sharedWorkspace];
}

- (id)init {
	if (!(self = [super init]))
		return nil;
	
	[self setHasUndoManager:NO];
	[self setUndoManager:nil];
	[self setShouldIgnoreChanges:YES];
	[self setShouldRecacheTextFiles:YES];
	[self setShouldRecacheProjectSymbolsArray:YES];
	
	return self;
}

- (void)dealloc {
#ifdef WCDEBUG
	NSLog(@"WCProject dealloc");
#endif
	
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsTabsAnimateAddingAndRemovingKey"];
	[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.kWCPrefsTabsAllowScrubbingOfTabsKey"];
	
	if (_buildTask) {
		if ([_buildTask isRunning])
			[_buildTask terminate];
	}
	
	[_projectGroups release];
	[_openTextFiles release];
	[_files release];
	[_projectPreferences release];
	[_buildStatusImagesAndStrings release];
	[_projectInfoString release];
	[_errorsAndWarnings release];
	[_findInProjectWindowController release];
	[_currentTextFileSymbols release];
	[_buildResultsWindowController release];
	[_buildTargetGetInfoWindowControllers release];
	[_fileGetInfoWindowControllers release];
	[_textFiles release];
	[_projectLabels release];
	[_projectEquates release];
	[_projectDefines release];
	[_projectMacros release];
	[_bookmarks release];
	[_targets release];
	[_steps release];
	[_buildProductsDirectoryPath release];
	[_projectSymbolsArray release];
	[_buildProductsOutputFilePaths release];
	[_textFileWindowControllers release];
	[super dealloc];
}

- (void)makeWindowControllers {
	[self addWindowController:[[[WCProjectWindowController alloc] initWithOwner:self] autorelease]];
}

- (void)windowControllerDidLoadNib:(NSWindowController *)windowController {
	[super windowControllerDidLoadNib:windowController];
	
	[windowController setShouldCloseDocument:YES];
	
	NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier:@"WCProjectWindowToolbar"] autorelease];
	
	// turn on autosaving for release builds
#ifndef WCDEBUG
	[toolbar setAutosavesConfiguration:YES];
#endif
	[toolbar setDisplayMode:NSToolbarDisplayModeDefault];
	[toolbar setSizeMode:NSToolbarSizeModeRegular];
	[toolbar setAllowsUserCustomization:YES];
	[toolbar setDelegate:self];
	[[windowController window] setToolbar:toolbar];
	
	[[self psmTabBarControl] performSetup];
	
	//[[[self projectOutlineView] tableColumnWithIdentifier:kWCProjectOutlineViewGroupsAndFilesColumnIdentifier] setHeaderCell:[[[WCGradientTableHeaderCell alloc] initTextCell:kWCProjectOutlineViewGroupsAndFilesColumnIdentifier] autorelease]];
	[[self projectOutlineView] setTarget:self];
	[[self projectOutlineView] setDoubleAction:@selector(_projectOutlineViewDoubleAction:)];
	[[self projectOutlineView] setMenu:[[WCInterfacePerformer sharedInstance] contextualMenuForProjectOutlineView]];
	[_projectOutlineViewAddButton setMenu:[[WCInterfacePerformer sharedInstance] menuForProjectOutlineViewAddButton]];
	[_projectOutlineViewActionButton setMenu:[[WCInterfacePerformer sharedInstance] menuForProjectOutlineViewActionButton]];

	[_projectTextViewActionButton setMenu:[[WCInterfacePerformer sharedInstance] menuForProjectTextViewActionButton]];
	
	[[self fileDetailTableView] setTarget:self];
	[[self fileDetailTableView] setDoubleAction:@selector(_fileTableViewDoubleAction:)];
	[[self fileDetailTableView] setMenu:[[WCInterfacePerformer sharedInstance] contextualMenuForProjectFileDetailTableView]];
	
	[[self errorsWarningsTableView] setTarget:self];
	[[self errorsWarningsTableView] setDoubleAction:@selector(_errorsAndWarningsTableViewDoubleAction:)];
	
	[[self projectLabelsTableView] setTarget:self];
	[[self projectLabelsTableView] setDoubleAction:@selector(_currentTextFileSymbolsTableViewDoubleAction:)];
	
	[_projectSymbolsTableView setTarget:self];
	[_projectSymbolsTableView setDoubleAction:@selector(_projectSymbolsTableViewDoubleAction:)];
	
	[[self projectGroup] setProject:self];
	[[self bookmarksGroup] setProject:self];
	
	//[self addObserver:self forKeyPath:@"fileURL" options:NSKeyValueObservingOptionNew context:(void *)@"fileURL"];
	
	[_projectBuildStatusTextField bind:@"imagesAndStrings" toObject:self withKeyPath:@"buildStatusImagesAndStrings" options:nil];

	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationDidBecomeActiveNotification:) name:NSApplicationDidBecomeActiveNotification object:NSApp];
	
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsTabsAnimateAddingAndRemovingKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsTabsAnimateAddingAndRemovingKey];
	[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.kWCPrefsTabsAllowScrubbingOfTabsKey" options:NSKeyValueObservingOptionNew context:(void *)kWCPrefsTabsAllowScrubbingOfTabsKey];
	
	// register to find out when files are edited
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_fileChangeCountDidChangeNotification:) name:kWCTFFileChangeCountDidChangeNotification object:nil];
	
	[self _loadProjectPreferences];
	[self setShouldIgnoreChanges:NO];
}

- (void)addWindowController:(NSWindowController *)wc {
	if ([wc isKindOfClass:[WCBuildTargetGetInfoWindowController class]]) {
		[[self buildTargetGetInfoWindowControllers] addObject:wc];
	}
	else if ([wc isKindOfClass:[WCFileGetInfoWindowController class]]) {
		[[self fileGetInfoWindowControllers] addObject:wc];
	}
	else if ([wc isKindOfClass:[WCTextFileWindowController class]]) {
		[[self textFileWindowControllers] addObject:wc];
	}
	[super addWindowController:wc];
}

- (void)removeWindowController:(NSWindowController *)wc {
	if ([wc isKindOfClass:[WCBuildTargetGetInfoWindowController class]]) {
		[[self buildTargetGetInfoWindowControllers] removeObject:wc];
	}
	else if ([wc isKindOfClass:[WCFileGetInfoWindowController class]]) {
		[[self fileGetInfoWindowControllers] removeObject:wc];
	}
	else if ([wc isKindOfClass:[WCTextFileWindowController class]]) {
		[[self textFileWindowControllers] removeObject:wc];
	}
	[super removeWindowController:wc];
}
#pragma mark IBActions
- (IBAction)saveDocument:(id)sender; {
	[self _updateProjectPreferences];
	[super saveDocument:sender];
	
	if (!sender)
		return;
	
	[[[self currentTabViewContext] currentTextFile] saveTextFile];
}

- (IBAction)saveDocumentAs:(id)sender; {
	[[[self currentTabViewContext] currentTextFile] saveTextFileAsAndCloseIfCancelled:NO];
}

- (IBAction)revertDocumentToSaved:(id)sender {
	[[[self currentTabViewContext] currentTextFile] revertTextFile];
}
#pragma mark Reading
- (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError {
	NSString *wrapperPath = [absoluteURL path];
	
	// load the project groups data, should be project.wcodedata file inside wrapper
	NSData *projectGroupsData = [NSData dataWithContentsOfFile:[self _projectDataPathFromWrapperPath:wrapperPath] options:NSUncachedRead error:outError];
	
	// bail if its not there
	if (!projectGroupsData)
		return NO;
	
	NSMutableArray *projectGroups = [NSKeyedUnarchiver unarchiveObjectWithData:projectGroupsData];
	
	if (!projectGroups) {
		// TODO: construct an error
		return NO;
	}
	
	_projectGroups = [projectGroups retain];
	
	// load the project prefs data, should be <user_name>.wcodeprefs file inside wrapper
	NSData *projectPreferencesData = [NSData dataWithContentsOfFile:[self _projectPreferencesPathFromWrapperPath:wrapperPath] options:NSUncachedRead error:outError];
	
	// bail if there wasn't one and load the default.wcodeprefs file instead
	if (!projectPreferencesData) {
		[self _loadDefaultProjectPreferences];
		return YES;
	}
	
	// turn the data into a property list
	NSString *errorString = nil;
	NSMutableDictionary *projectPreferences = [NSPropertyListSerialization propertyListFromData:projectPreferencesData mutabilityOption:NSPropertyListMutableContainers format:NULL errorDescription:&errorString];
	
	// bail if the conversion fails and load the default.wcodeprefs file instead
	if (!projectPreferences) {
		if (errorString != NULL) {
			NSLog(@"readFromURL: %@", errorString);
			[errorString release];
		}
		[self _loadDefaultProjectPreferences];
		return YES;
	}
	
	_projectPreferences = [projectPreferences retain];
	
	return YES;
}
#pragma mark Writing
- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError {
	// create a new wrapper which holds our .wcodedata and .wcodeprefs files
	NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initDirectoryWithFileWrappers:nil] autorelease];
	NSString *wrapperPath = [absoluteURL path];
	
	// write the wrapper file to disk, bail if the write fails
	if (![wrapper writeToFile:wrapperPath atomically:YES updateFilenames:NO]) {
		// TODO: construct an error
		return NO;
	}
	
	NSData *projectGroupsData = [NSKeyedArchiver archivedDataWithRootObject:_projectGroups];
	
	if (!projectGroupsData) {
		// TODO: construct an error
		return NO;
	}
	
	if (![projectGroupsData writeToFile:[self _projectDataPathFromWrapperPath:wrapperPath] options:NSAtomicWrite error:outError]) {
		return NO;
	}
		
	// turn our current project prefs into data
	NSString *errorString = nil;
	NSData *projectPreferencesData = [NSPropertyListSerialization dataFromPropertyList:[self projectPreferences] format:NSPropertyListXMLFormat_v1_0 errorDescription:&errorString];
	
	if (!projectPreferencesData) {
		if (errorString != NULL) {
			NSLog(@"writeToURL: %@, %@", errorString, [self projectPreferences]);
			[errorString release];
		}
	}
	
	if (![projectPreferencesData writeToFile:[self _projectPreferencesPathFromWrapperPath:wrapperPath] options:NSAtomicWrite error:outError]) {

	}
	return YES;
}
#pragma mark NSSplitView Delegate
- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview {
	if (![splitView isVertical] && [[[splitView subviews] objectAtIndex:0] isEqualTo:subview])
		return YES;
	return NO;
}

- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex {
	if (![splitView isVertical] && [[[splitView subviews] objectAtIndex:0] isEqualTo:subview])
		return YES;
	return NO;
}

- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset {
	if ([splitView isVertical] && offset == 0)
		return proposedMin + 150.0;
	else if ([splitView isVertical] && offset == 1)
		return proposedMin + 150.0;
	else if (![splitView isVertical])
		return proposedMin + 100.0;
	return proposedMin;
}

- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset {
	if ([splitView isVertical] && offset == 0)
		return proposedMax - 150.0;
	else if ([splitView isVertical] && offset == 1)
		return proposedMax - 150.0;
	else if (![splitView isVertical])
		return proposedMax - 250.0;
	return proposedMax;
}

- (NSRect)splitView:(NSSplitView *)splitView additionalEffectiveRectOfDividerAtIndex:(NSInteger)dividerIndex {
	if ([splitView isVertical]) {
		switch (dividerIndex) {
			case 0: // left divider with the project outline view
				return [splitView convertRect:[_leftSplitterHandleImageView frame] fromView:nil];
			case 1: // right divider with the symbols table view
				return [splitView convertRect:[_rightSplitterHandleImageView frame] fromView:_rightSplitterHandleImageView];
			default:
				return NSZeroRect;
		}
	}
	return NSZeroRect;
}

- (void)splitView:(NSSplitView *)splitView resizeSubviewsWithOldSize:(NSSize)oldSize {
	if ([splitView inLiveResize]) {
		// keep the project outline view and symbols table view the same size while the resizing the middle view
		if ([splitView isVertical]) {
			NSArray *subviews = [splitView subviews];
			NSView *leftView = [subviews objectAtIndex:0];
			NSView *middleView = [subviews objectAtIndex:1];
			NSView *rightView = [subviews lastObject];
			NSRect currentFrame = [splitView frame];
			NSRect leftFrame = [leftView frame];
			NSRect middleFrame = [middleView frame];
			NSRect rightFrame = [rightView frame];
			CGFloat dividerThickness = [splitView dividerThickness];
			
			leftFrame.size.height = currentFrame.size.height;
			middleFrame.size.width = currentFrame.size.width - leftFrame.size.width - rightFrame.size.width - dividerThickness - dividerThickness;
			middleFrame.size.height = currentFrame.size.height;
			middleFrame.origin.x = leftFrame.size.width + dividerThickness;
			rightFrame.size.height = currentFrame.size.height;
			rightFrame.origin.x = leftFrame.size.width + middleFrame.size.width + dividerThickness + dividerThickness;
			
			[leftView setFrame:leftFrame];
			[middleView setFrame:middleFrame];
			[rightView setFrame:rightFrame];
		}
		// keep the top view in the middle split view the same while resizing the bottom view
		else {
			NSArray *subviews = [splitView subviews];
			NSView *topView = [subviews objectAtIndex:0];
			
			// let the split view take over if the top view is already collapsed, otherwise strange things happen
			if ([splitView isSubviewCollapsed:topView]) {
				[splitView adjustSubviews];
				return;
			}
			
			NSView *bottomView = [subviews lastObject];
			NSRect currentFrame = [splitView frame];
			NSRect topFrame = [topView frame];
			NSRect bottomFrame = [bottomView frame];
			CGFloat dividerThickness = [splitView dividerThickness];
			
			topFrame.size.width = currentFrame.size.width;
			bottomFrame.size.height = currentFrame.size.height - topFrame.size.height - dividerThickness;
			bottomFrame.size.width = currentFrame.size.width;
			bottomFrame.origin.y = topFrame.size.height + dividerThickness;
			
			[topView setFrame:topFrame];
			[bottomView setFrame:bottomFrame];
		}
	}
	else {
		[splitView adjustSubviews];
	}
}
#pragma mark NSOutlineView Delegate
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item {
	if ([[tableColumn identifier] isEqualToString:kWCProjectOutlineViewGroupsAndFilesColumnIdentifier]) {
		id object = [item representedObject];
		
		[cell setIcon:[object icon]];
		[cell setIconSize:NSMakeSize(16.0, 16.0)];
		[cell setBadgeCount:0];
		
		
		if ([object isKindOfClass:[WCFile class]]) {
			[cell setOnlySelectFileName:YES];
			
			if ([object isValid])
				return;
			
			NSMutableAttributedString *string = [[[cell attributedStringValue] mutableCopy] autorelease];
			
			[string addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithCalibratedRed:1.0 green:0.0 blue:0.0 alpha:1.0] range:[string wholeRange]];
			
			[cell setAttributedStringValue:string];
			 
			 
		}
		else if ([object isKindOfClass:[WCBuildMessage class]]) {
			switch ([object messageType]) {
				case WCBMTypeFile:
					[cell setBadgeCount:[object numberOfErrorsAndWarnings]];
					break;
				case WCBMTypeError:
				case WCBMTypeWarning:
					[cell setIconSize:NSMakeSize(14.0, 14.0)];
				default:
					break;
			}
		}
		else if ([object isKindOfClass:[WCErrorsAndWarningsGroup class]]) {
			[cell setBadgeCount:[object numberOfErrorsAndWarnings]];
		}
	}
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item {
	if ([[tableColumn identifier] isEqualToString:kWCProjectOutlineViewGroupsAndFilesColumnIdentifier]) {
		return [[item representedObject] canRenameNode];
	}
	return YES;
}

- (NSString *)outlineView:(NSOutlineView *)ov toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tc item:(id)item mouseLocation:(NSPoint)mouseLocation {
	return [[item representedObject] nodeToolTip];
}

- (void)outlineViewColumnDidResize:(NSNotification *)note {
	//[[[note object] cornerView] setNeedsDisplay:YES];
}

- (void)outlineViewSelectionDidChange:(NSNotification *)note {
	[self updateSwapView];
}
#pragma mark NSTableView Delegate
- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
	if ([[tableColumn identifier] isEqualToString:kWCProjectFileTableViewNameColumnIdentifier]) {
		WCFile *file = [[[self fileDetailArrayController] arrangedObjects] objectAtIndex:row];
		
		[cell setOnlySelectFileName:YES];
		
		if ([file isValid])
			return;
		
		NSMutableAttributedString *string = [[[cell attributedStringValue] mutableCopy] autorelease];
		
		[string addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithCalibratedRed:1.0 green:0.0 blue:0.0 alpha:1.0] range:[string wholeRange]];
		
		[cell setAttributedStringValue:string];
	}
}

- (NSString *)tableView:(NSTableView *)tableView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouse {
	if ([[tableColumn identifier] isEqualToString:kWCProjectFileTableViewNameColumnIdentifier]) {
		return [[[[self fileDetailArrayController] arrangedObjects] objectAtIndex:row] nodeToolTip];
	}
	else if ([[tableColumn identifier] isEqualToString:kWCProjectCurrentTextFileSymbolsSymbolNameColumnIdentifier]) {
		WCSymbol *symbol = [[[tableView dataSource] arrangedObjects] objectAtIndex:row];
		return [NSString stringWithFormat:@"Type: %@\nName: %@\nLine: %u", [symbol symbolTypeAsString],[symbol name],[symbol lineNumber]+1];
	}
	return nil;
}
#pragma mark NSControl Delegate
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor {
	return ([[fieldEditor string] length] == 0)?NO:YES;
}
#pragma mark NSWindow Delegate

- (BOOL)windowShouldClose:(id)window {
	NSArray *files = [self unsavedTextFiles];
	if (![files count])
		return YES;
	
	NSInteger result = [WCUnsavedFilesWindowController runModalForUnsavedFiles:files type:WCUnsavedFilesTypeBeforeClosingProject];
	
	if (result == NSCancelButton)
		return NO;
	return YES;
}

- (void)windowWillClose:(NSNotification *)note {
	//[self removeObserver:self forKeyPath:@"fileURL"];
	
	[_projectBuildStatusTextField unbind:@"imagesAndStrings"];
	[_lineColumnButton unbind:@"title"];
	
	[[[self findInProjectWindowControllerDontCreate] projectBuildStatusTextField] unbind:@"imagesAndStrings"];
	[[[self findInProjectWindowControllerDontCreate] projectStatusTextField] unbind:@"value"];
	
	[[[self buildResultsWindowControllerDontCreate] projectStatusTextField] unbind:@"value"];
	[[[self buildResultsWindowControllerDontCreate] projectBuildStatusTextField] unbind:@"imagesAndStrings"];
	[[[[self buildResultsWindowControllerDontCreate] outlineView] dataSource] unbind:@"contentObject"];
	
	[[self buildResultsWindowControllerDontCreate] setDocument:nil];
	
	[[self findInProjectWindowControllerDontCreate] setDocument:nil];
	
	[self saveDocument:nil];
	
	[self removeAllTabViewItemsInTabViewContext:[self findInProjectWindowControllerDontCreate]];
	[self removeAllTabViewItemsInTabViewContext:[self buildResultsWindowControllerDontCreate]];
	[self removeAllTabViewItemsInTabViewContext:self];
	
	// this means we are the last project, reset the dock badges
	if ([[[WCDocumentController sharedDocumentController] allProjects] count] == 1)
		[[WCDockBadgeView sharedInstance] resetErrorAndWarningCount];
}
#pragma mark NSToolbar Delegate
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)identifier willBeInsertedIntoToolbar:(BOOL)flag {
	NSToolbarItem *item = [[WCBasicPerformer sharedInstance] toolbarItemForItemIdentifier:identifier inProject:self];
	
	return item;
}

- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
	return [NSArray arrayWithObjects:kWCProjectToolbarProjectOverviewIdentifier, kWCProjectToolbarBuildIdentifier,kWCProjectToolbarCountCodeIdentifier,kWCProjectToolbarBuildResultsWindowIdentifier,kWCProjectToolbarFindInProjectWindowIdentifier,kWCProjectToolbarGetInfoIdentifier,kWCProjectToolbarSearchIdentifier, NSToolbarSpaceItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, NSToolbarSeparatorItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier, nil];
}

- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
	return [NSArray arrayWithObjects:kWCProjectToolbarProjectOverviewIdentifier,NSToolbarSpaceItemIdentifier,kWCProjectToolbarCountCodeIdentifier,NSToolbarSpaceItemIdentifier,kWCProjectToolbarBuildIdentifier,NSToolbarSpaceItemIdentifier,kWCProjectToolbarBuildResultsWindowIdentifier,kWCProjectToolbarFindInProjectWindowIdentifier,NSToolbarSpaceItemIdentifier,kWCProjectToolbarGetInfoIdentifier, NSToolbarFlexibleSpaceItemIdentifier, kWCProjectToolbarSearchIdentifier, nil];
}

- (void)toolbarWillAddItem:(NSNotification *)note {
	NSToolbarItem *item = [[note userInfo] objectForKey:@"item"];
	
	if ([[item itemIdentifier] isEqualToString:kWCProjectToolbarSearchIdentifier]) {
		_projectSearchField = (NSSearchField *)[item view];
	}
}

- (void)toolbarDidRemoveItem:(NSNotification *)note {
	NSToolbarItem *item = [[note userInfo] objectForKey:@"item"];
	
	if ([[item itemIdentifier] isEqualToString:kWCProjectToolbarSearchIdentifier]) {
		_projectSearchField = nil;
	}
}
#pragma mark PSMTabBarControl Delegate
- (BOOL)tabView:(NSTabView *)aTabView shouldCloseTabViewItem:(NSTabViewItem *)item; {
	return [self canRemoveTabViewItem:item inTabViewContext:self];
}
- (void)tabView:(NSTabView *)aTabView didCloseTabViewItem:(NSTabViewItem *)item; {
	[self removeTabViewItem:item inTabViewContext:self saveTextFile:NO];
	[self setCurrentTextFileSymbols:[[self currentTextFile] fileSymbolsArray]];
	if ([self currentTextView]) {
		[_lineColumnButton bind:@"title" toObject:[self currentTextView] withKeyPath:@"infoString" options:nil];
		[[WCSyntaxHighlighter sharedInstance] highlightTextView:[[item view] documentView]];
	}
	else {
		[_lineColumnButton unbind:@"title"];
		[_lineColumnButton setTitle:NSLocalizedString(@"N/A",@"N/A")];
	}
}

- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)item {
	[self setCurrentTextFileSymbols:[[item identifier] fileSymbolsArray]];
	[_lineColumnButton bind:@"title" toObject:[[item view] documentView] withKeyPath:@"infoString" options:nil];
	[[WCSyntaxHighlighter sharedInstance] highlightTextView:[[item view] documentView]];
}

- (BOOL)tabView:(NSTabView *)tabView shouldDragTabViewItem:(NSTabViewItem *)tabViewItem fromTabBar:(PSMTabBarControl *)tabBarControl; {
	return ([tabView numberOfTabViewItems] == 1)?NO:YES;
}
- (BOOL)tabView:(NSTabView *)tabView shouldDropTabViewItem:(NSTabViewItem *)tabViewItem inTabBar:(PSMTabBarControl *)tabBarControl; {
	if ([[tabBarControl tabView] isEqualTo:tabView])
		return YES;
	return NO;
}
- (BOOL)tabView:(NSTabView *)tabView shouldAllowTabViewItem:(NSTabViewItem *)tabViewItem toLeaveTabBar:(PSMTabBarControl *)tabBarControl; {
	return NO;
}

#pragma mark NSUserInterfaceValidations Protocol
- (BOOL)validateMenuItem:(NSMenuItem *)item; {
	SEL action = [item action];
	
	if (action == @selector(renameAction:)) {
		NSArray *nodes = [self selectedNodes];
		
		if ([nodes count] != 1)
			return NO;
		
		if (![[[nodes firstObject] representedObject] canRenameNode])
			return NO;
	}
	else if (action == @selector(delete:)) {		
		return [self canDeleteObjects:[self selectedRepresentedObjects]];
	}
	else if (action == @selector(groupAction:)) {
		for (NSTreeNode *node in [self selectedNodes])
			if (![[node representedObject] canGroupNode])
				return NO;
	}
	else if (action == @selector(ungroupAction:)) {
		for (NSTreeNode *node in [self selectedNodes])
			if (![[node representedObject] canUngroupNode])
				return NO;
	}
	else if (action == @selector(openWithFinderAction:)) {
		for (NSTreeNode *node in [self selectedNodes])
			if (![[node representedObject] isKindOfClass:[WCFile class]])
				return NO;
	}
	else if (action == @selector(revealInFinderAction:)) {
		for (NSTreeNode *node in [self selectedNodes])
			if (![[node representedObject] isKindOfClass:[WCFile class]])
				return NO;
	}
	else if (action == @selector(getInfoAction:)) {
		for (NSTreeNode *node in [self selectedNodes])
			if (![[node representedObject] canGetInfo])
				return NO;
	}
	else if (action == @selector(revealInGroupTreeAction:)) {
		if (![[[self projectWindow] firstResponder] isEqualTo:[self fileDetailTableView]])
			return NO;
		
		if ([[[self fileDetailArrayController] selectedObjects] count] != 1)
			return NO;
	}
	else if (action == @selector(buildAction:)) {
		if ([self isBuilding])
			return NO;
	}
	else if (action == @selector(buildAndRunAction:)) {
		if ([self isBuilding])
			return NO;
	}
	else if (action == @selector(buildAndDebugAction:)) {
		if ([self isBuilding])
			return NO;
	}
	else if (action == @selector(makeActiveTargetAction:)) {
		NSArray *nodes = [[self projectTreeController] selectedNodes];
		
		if ([nodes count] != 1)
			return NO;
		else if (![[[nodes objectAtIndex:0] representedObject] isKindOfClass:[WCBuildTarget class]])
			return NO;
		else if ([[[nodes objectAtIndex:0] representedObject] isTargetActive])
			return NO;
	}
	else if (action == @selector(revertDocumentToSaved:)) {
		WCTextFile *tf = [[self currentTabViewContext] currentTextFile];
		if (!tf || ![tf isEdited])
			return NO;
	}
	else if (action == @selector(saveDocumentAs:)) {
		if (![[self currentTabViewContext] currentTextFile])
			return NO;
	}
	else if (action == @selector(countCodeAction:)) {
		WCTextView *tv = [[self currentTabViewContext] currentTextView];
		if (!tv)
			return NO;
		else if (![tv selectedRange].length)
			return NO;
	}
	return YES;
}

- (BOOL)validateToolbarItem:(NSToolbarItem *)item {
	SEL action = [item action];
	
	if (action == @selector(buildAction:)) {
		if ([self isBuilding])
			return NO;
	}
	else if (action == @selector(buildAndRunAction:)) {
		if ([self isBuilding])
			return NO;
	}
	else if (action == @selector(buildAndDebugAction:)) {
		if ([self isBuilding])
			return NO;
	}
	else if (action == @selector(getInfoAction:)) {
		for (NSTreeNode *node in [self selectedNodes])
			if (![[node representedObject] canGetInfo])
				return NO;
	}
	else if (action == @selector(countCodeAction:)) {
		NSWindow *window = [NSApp keyWindow];
		
		if ([window isEqualTo:[self projectWindow]] || [[window windowController] conformsToProtocol:@protocol(WCTabViewContext)]) {
			WCTextView *tv = [[self currentTabViewContext] currentTextView];
			if (!tv)
				return NO;
			else if (![tv selectedRange].length)
				return NO;
		}
		else if (![[window windowController] isKindOfClass:[WCTextFileWindowController class]])
			return NO;
	}
	return YES;
}
#pragma mark NSKeyValueObserving Protocol
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	if ([(NSString *)context isEqualToString:kWCPrefsTabsAnimateAddingAndRemovingKey]) {
		[[self psmTabBarControl] setAutomaticallyAnimates:NSUserDefaultsBoolForKey(kWCPrefsTabsAnimateAddingAndRemovingKey)];
	}
	else if ([(NSString *)context isEqualToString:kWCPrefsTabsAllowScrubbingOfTabsKey]) {
		[[self psmTabBarControl] setAllowsScrubbing:NSUserDefaultsBoolForKey(kWCPrefsTabsAllowScrubbingOfTabsKey)];
	}
	/*
	else if ([(NSString *)context isEqualToString:@"fileURL"]) {
		[[self projectOutlineView] setNeedsDisplay:YES];
		[[self fileDetailTableView] setNeedsDisplay:YES];
	}
	 */
	else {
		[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
	}
}
#pragma mark -
#pragma mark *** Public Methods ***

- (WCFile *)fileMatchingAbsoluteFilePath:(NSString *)absoluteFilePath; {
	for (WCFile *file in [[self projectGroup] descendantLeafNodes]) {
		if ([[file absoluteFilePath] isEqualToString:absoluteFilePath])
			return file;
	}
	return nil;
}

- (WCGroup *)groupForNewFileInsertionAndInsertionIndex:(NSUInteger *)insertionIndex; {
	NSArray *selectedObjects = [self selectedNodes];
	
	if (![selectedObjects count]) {
		if (insertionIndex != NULL) {
			*insertionIndex = 0;
		}
		return [self projectGroup];
	}
	
	id node = [selectedObjects firstObject];
	
	if (![[node representedObject] isKindOfClass:[WCFile class]]) {
		if (insertionIndex != NULL) {
			*insertionIndex = 0;
		}
		return [self projectGroup];
	}
	
	if ([[node representedObject] isKindOfClass:[WCGroup class]]) {
		if (insertionIndex != NULL) {
			*insertionIndex = 0;
		}
		return [node representedObject];
	}
	
	if (insertionIndex != NULL) {
		*insertionIndex = [[node indexPath] lastIndex];
	}
	return [[node parentNode] representedObject];
}

- (void)updateSwapView; {
	NSArray *nodes = [[[[self projectOutlineView] dataSource] selectedNodes] valueForKey:@"representedObject"];
	NSMutableArray *files = [NSMutableArray array];
	NSMutableArray *bookmarks = [NSMutableArray array];
	NSMutableArray *errorsAndWarnings = [NSMutableArray array];
	BOOL errorsAndWarningsSelected = YES;
	BOOL bookmarksSelected = YES;
	BOOL projectSymbolsSelected = YES;
	
	for (id node in nodes) {
		if (errorsAndWarningsSelected &&
			![node isKindOfClass:[WCErrorsAndWarningsGroup class]] &&
			![node isKindOfClass:[WCBuildMessage class]]) {
			
			errorsAndWarningsSelected = NO;
		}
		if (bookmarksSelected &&
				![node isKindOfClass:[WCBookmarksGroup class]] &&
				 ![node isKindOfClass:[WCBookmark class]]) {
			
			bookmarksSelected = NO;
		}
		if (projectSymbolsSelected &&
			![node isKindOfClass:[WCProjectSymbolsGroup class]]) {
			
			projectSymbolsSelected = NO;
		}
		
		if ([node isKindOfClass:[WCFile class]] ||
			[node isKindOfClass:[WCBuildProductsGroup class]])
			[files addObject:node];
		else if ([node isKindOfClass:[WCBookmark class]] ||
				[node isKindOfClass:[WCBookmarksGroup class]])
			[bookmarks addObject:node];
		else if ([node isKindOfClass:[WCBuildMessage class]] ||
				[node isKindOfClass:[WCErrorsAndWarningsGroup class]])
			[errorsAndWarnings addObject:node];
	}
	
	if (errorsAndWarningsSelected) {
		static NSArray *sortDescriptors = nil;
		if (!sortDescriptors) {
			NSSortDescriptor *first = [[[NSSortDescriptor alloc] initWithKey:@"fileName" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)] autorelease];
			NSSortDescriptor *second = [[[NSSortDescriptor alloc] initWithKey:@"lineNumberAsObject" ascending:YES selector:@selector(compare:)] autorelease];
			sortDescriptors = [[NSArray alloc] initWithObjects:first, second, nil];
		}
		[self setErrorsAndWarnings:[[errorsAndWarnings valueForKeyPath:@"@distinctUnionOfArrays.descendantLeafNodesInclusive"] sortedArrayUsingDescriptors:sortDescriptors]];
		
		if (_currentSwapView != _errorsAndWarningsView) {
			[_projectSearchField setStringValue:@""];
			[[_fileTableView dataSource] setFilterPredicate:nil];
			[[_bookmarksTableView dataSource] setFilterPredicate:nil];
			[[_projectSymbolsTableView dataSource] setFilterPredicate:nil];
			[self setCurrentView:_errorsAndWarningsView];
		}
	}
	else if (bookmarksSelected) {
		[self setBookmarks:[bookmarks valueForKeyPath:@"@distinctUnionOfArrays.descendantLeafNodesInclusive"]];
		
		if (_currentSwapView != _bookmarksView) {
			[_projectSearchField setStringValue:@""];
			[[_fileTableView dataSource] setFilterPredicate:nil];
			[[_errorsAndWarningsTableView dataSource] setFilterPredicate:nil];
			[[_projectSymbolsTableView dataSource] setFilterPredicate:nil];
			[self setCurrentView:_bookmarksView];
		}
	}
	else if (projectSymbolsSelected) {
		if (_shouldRecacheProjectSymbolsArray) {
			_shouldRecacheProjectSymbolsArray = NO;
			
			NSMutableArray *symbols = [NSMutableArray array];
			
			for (WCTextFile *tf in [self textFiles]) {
				[symbols addObjectsFromArray:[tf fileSymbolsArray]];
			}
			
			[self setProjectSymbolsArray:symbols];
		}
		
		if (_currentSwapView != _projectSymbolsView) {
			[_projectSearchField setStringValue:@""];
			[[_fileTableView dataSource] setFilterPredicate:nil];
			[[_errorsAndWarningsTableView dataSource] setFilterPredicate:nil];
			[[_bookmarksTableView dataSource] setFilterPredicate:nil];
			[self setCurrentView:_projectSymbolsView];
		}
	}
	else {
		if ([self shouldAllowEmptyFileDetailTableView] || [files count]) {
			[self setShouldAllowEmptyFileDetailTableView:NO];
			[self setFiles:[files valueForKeyPath:@"@distinctUnionOfArrays.descendantLeafNodesInclusive"]];
		}
		
		if (_currentSwapView != _fileView) {
			[_projectSearchField setStringValue:@""];
			[[_errorsAndWarningsTableView dataSource] setFilterPredicate:nil];
			[[_bookmarksTableView dataSource] setFilterPredicate:nil];
			[[_projectSymbolsTableView dataSource] setFilterPredicate:nil];
			[self setCurrentView:_fileView];
		}
	}
}

- (void)updateAllTabViewContexts; {
	for (NSObject <WCTabViewContext> *context in [self allTabViewContexts]) {
		[[WCSyntaxHighlighter sharedInstance] highlightTextView:[context currentTextView]];
	}
}

- (BOOL)buildTargetExistsWithModel:(WCBTModel)model output:(WCBTOutputType)output; {
	for (WCBuildTarget *target in [[self buildTargetsGroup] childNodes]) {
		if ([target targetModel] == model && [target targetOutputType] == output)
			return YES;
	}
	return NO;
}

- (BOOL)canDeleteObjects:(NSArray *)objects; {
	if (![objects count])
		return NO;
	
	// check to see if any of the objects override to explicitly prevent deletion
	for (id object in objects) {
		if (![object canDeleteNode])
			return NO;
	}
	
	id firstObject = [objects firstObject];
	
	// special case when dealing with files, make sure everything is of the same base class
	if ([firstObject isKindOfClass:[WCFile class]]) {
		Class fileClass = [WCFile class];
		
		for (id object in objects) {
			if (![object isKindOfClass:fileClass])
				return NO;
		}
	}
	else if ([firstObject isKindOfClass:[WCBuildTarget class]]) {
		Class targetClass = [WCBuildTarget class];
		
		for (WCBuildTarget *target in objects) {
			if (![target isKindOfClass:targetClass])
				return NO;
			else if ([target isTargetActive])
				return NO;
		}
	}
	else {
		Class class = [firstObject class];
		
		for (id object in objects) {
			if (![object isKindOfClass:class])
				return NO;
		}
	}
	
	return YES;
}

- (void)deleteObjects:(NSArray *)objects moveFilesToTrash:(BOOL)moveFilesToTrash; {
	if ([[objects firstObject] isKindOfClass:[WCFile class]]) {
		[self setShouldAllowEmptyFileDetailTableView:YES];
		if ([[[self bookmarksGroup] childNodes] count]) {
			NSMutableArray *bookmarksToRemove = [NSMutableArray array];
			for (WCFile *file in objects) {
				if (![file isKindOfClass:[WCTextFile class]])
					continue;
				
				for (WCBookmark *bookmark in [[self bookmarksGroup] childNodes]) {
					if ([[bookmark textFile] isEqualTo:file]) {
						[bookmarksToRemove addObject:bookmark];
						break;
					}
				}
			}
			[[[self bookmarksGroup] mutableChildNodes] removeObjectsInArray:bookmarksToRemove];
		}
		NSMutableArray *stepsToRemove = [NSMutableArray array];
		for (WCBuildTarget *target in [[self buildTargetsGroup] childNodes]) {
			for (WCBuildStep *step in [target childNodes]) {
				if ([objects containsObject:[step file]]) {
					[stepsToRemove addObject:step];
				}
			}
			[[target mutableChildNodes] removeObjectsInArray:stepsToRemove];
			[stepsToRemove removeAllObjects];
		}
		for (id file in objects) {
			if ([file isKindOfClass:[WCTextFile class]]) {
				[self removeAllTabViewItemsForTextFile:file];
			}
		}
	}	
	
	if (moveFilesToTrash) {
		NSInteger tag = 0;
		for (WCFile *file in objects) {
			if ([file isKindOfClass:[WCFile class]]) {
				if (![[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:[file folderPath] destination:@"" files:[NSArray arrayWithObject:[file name]] tag:&tag]) {
					// TODO: construct an error and display it
				}
			}
		}
	}
	
	[[self projectTreeController] removeRepresentedObjects:objects];
	[self setShouldRecacheTextFiles:YES];
	[self setShouldRecacheProjectSymbols:YES];
	[self setShouldRecacheProjectSymbolsArray:YES];
	[self updateAllTabViewContexts];
	[self updateSwapView];
}

- (void)removeAllBuildErrorsAndWarnings; {
	for (WCTextFile *tf in [self textFiles]) {
		[tf removeAllFileErrors];
		[tf removeAllFileWarnings];
	}
}

- (void)removeAllBuildMessages; {
	[self setRootBuildMessage:nil];
	[[[self errorsAndWarningsGroup] mutableChildNodes] removeAllObjects];
}

- (id)showGetInfoWindowForItem:(id)item; {	
	if ([item isKindOfClass:[WCBuildTarget class]]) {
		for (WCBuildTargetGetInfoWindowController *controller in [self buildTargetGetInfoWindowControllers]) {
			if ([[controller buildTarget] isEqualTo:item]) {
				[controller showWindow:nil];
				return controller;
			}
		}
		
		WCBuildTargetGetInfoWindowController *wc = [WCBuildTargetGetInfoWindowController getInfoWindowControllerWithBuildTarget:item];
		[self addWindowController:wc];
		[wc showWindow:nil];
		return wc;
	}
	else if ([item isKindOfClass:[WCBuildConfiguration class]]) {
		for (WCBuildTargetGetInfoWindowController *controller in [self buildTargetGetInfoWindowControllers]) {
			if ([[controller buildTarget] isEqualTo:[item buildTarget]]) {
				[controller showWindow:nil];
				[[controller tabView] selectTabViewItemWithIdentifier:kWCBuildTargetGetInfoWindowBuildOptionsTabIdentifier];
				return controller;
			}
		}
		
		WCBuildTargetGetInfoWindowController *wc = [WCBuildTargetGetInfoWindowController getInfoWindowControllerWithBuildTarget:[item buildTarget]];
		[self addWindowController:wc];
		[wc showWindow:nil];
		[[wc tabView] selectTabViewItemWithIdentifier:kWCBuildTargetGetInfoWindowBuildOptionsTabIdentifier];
		return wc;
	}
	else if ([item isKindOfClass:[WCBuildStep class]]) {
		for (WCBuildTargetGetInfoWindowController *controller in [self buildTargetGetInfoWindowControllers]) {
			if ([[controller buildTarget] isEqualTo:[item buildTarget]]) {
				[controller showWindow:nil];
				[[controller tabView] selectTabViewItemWithIdentifier:kWCBuildTargetGetInfoWindowBuildStepsTabIdentifier];
				return controller;
			}
		}
		
		WCBuildTargetGetInfoWindowController *wc = [WCBuildTargetGetInfoWindowController getInfoWindowControllerWithBuildTarget:[item buildTarget]];
		[self addWindowController:wc];
		[wc showWindow:nil];
		[[wc tabView] selectTabViewItemWithIdentifier:kWCBuildTargetGetInfoWindowBuildStepsTabIdentifier];
		return wc;
	}
	else if ([item isKindOfClass:[WCFile class]]) {
		for (WCFileGetInfoWindowController *controller in [self fileGetInfoWindowControllers]) {
			if ([[controller file] isEqualTo:item]) {
				[controller showWindow:nil];
				return controller;
			}
		}
		
		WCFileGetInfoWindowController *wc = [WCFileGetInfoWindowController getInfoWindowControllerWithFile:item];
		[self addWindowController:wc];
		[wc showWindow:nil];
		return wc;
	}
	return nil;
}

- (void)cycleToNextFirstResponder; {
	NSResponder *responder = [[self projectWindow] firstResponder];
	
	if ([responder isKindOfClass:[WCProjectOutlineView class]]) {
		[[self projectWindow] makeFirstResponder:[[[_swapView subviews] firstObject] documentView]];
	}
	else if ([responder isKindOfClass:[WCKeyDownTableView class]]) {
		if ([responder isEqualTo:_currentTextFileSymbolsTableView])
			[[self projectWindow] makeFirstResponder:[self currentTextView]];
		else
			[[self projectWindow] makeFirstResponder:_currentTextFileSymbolsTableView];
	}
	else if ([responder isEqualTo:_projectSearchField]) {
		[[self projectWindow] makeFirstResponder:_currentTextFileSymbolsTableView];
	}
	else if ([responder isKindOfClass:[NSTextView class]]) {
		[[self projectWindow] makeFirstResponder:_projectOutlineView];
	}
}

- (void)cycleToPreviousFirstResponder; {
	NSResponder *responder = [[self projectWindow] firstResponder];
	
	if ([responder isKindOfClass:[WCProjectOutlineView class]]) {
		[[self projectWindow] makeFirstResponder:[self currentTextView]];
		
	}
	else if ([responder isKindOfClass:[WCKeyDownTableView class]]) {
		if ([responder isEqualTo:_currentTextFileSymbolsTableView])
			[[self projectWindow] makeFirstResponder:[[[_swapView subviews] firstObject] documentView]];
		else
			[[self projectWindow] makeFirstResponder:_projectOutlineView];
	}
	else if ([responder isKindOfClass:[NSTextView class]]) {
		[[self projectWindow] makeFirstResponder:_currentTextFileSymbolsTableView];
	}
}

- (WCTextFileWindowController *)createSeparateEditorForTextFile:(WCTextFile *)textFile; {
	for (WCTextFileWindowController *wc in [self textFileWindowControllers]) {
		if ([textFile isEqualTo:[wc textFile]]) {
			[wc showWindow:nil];
			return wc;
		}
	}
	
	WCTextFileWindowController *wc = [WCTextFileWindowController textFileWindowWithTextFile:textFile];
	[self addWindowController:wc];
	[wc showWindow:nil];
	return wc;
}
#pragma mark Tabs
- (NSTabViewItem *)addTabViewItemForTextFile:(WCTextFile *)textFile inTabViewContext:(NSObject <WCTabViewContext> *)context; {
	// check to see if the text file is valid
	if (![textFile isValid])
		return nil;
	
	// select the tab if it already exists
	if ([self selectTabViewItemWithIdentifier:textFile inTabViewContext:context])
		return [self currentTabViewItem];
	
	// increase the open count for the text file
	[[self openTextFiles] addObject:textFile];
	
	NSScrollView *scrollView = [[WCInterfacePerformer sharedInstance] scrollViewWithTextViewForTextFile:textFile inProject:self];
	NSTabViewItem *item = [[[NSTabViewItem alloc] initWithIdentifier:textFile] autorelease];
	[item bind:@"label" toObject:textFile withKeyPath:@"name" options:nil];
	[item setView:scrollView];
	[item setInitialFirstResponder:[scrollView documentView]];
	
	[[context tabView] addTabViewItem:item];
	
	[[scrollView documentView] setSelectedRangeSafely:[textFile fileSelectedRange]];
	[[scrollView documentView] scrollRangeToVisible:[[scrollView documentView] selectedRange]];
	
	if (!NSUserDefaultsBoolForKey(kWCPrefsTabsOpenNewTabsInBackgroundKey)) {
		[[context tabView] selectTabViewItem:item];
	}
	return item;
}

- (BOOL)canRemoveTabViewItem:(NSTabViewItem *)tabViewItem inTabViewContext:(NSObject <WCTabViewContext> *)context; {
	WCTextFile *textFile = [tabViewItem identifier];
	
	if ([[self openTextFiles] countForObject:textFile] > 1)
		return YES;
	// if the file hasn't been edited, close it without confirmation
	else if (![textFile isEdited])
		return YES;
	
	NSInteger result = [WCUnsavedFilesWindowController runModalForUnsavedFiles:[NSArray arrayWithObject:textFile] type:WCUnsavedFilesTypeBeforeClosingFile];
	
	if (result == NSCancelButton)
		return NO;
	return YES;
}

- (void)removeTabViewItem:(NSTabViewItem *)tabViewItem inTabViewContext:(NSObject <WCTabViewContext> *)context saveTextFile:(BOOL)flag; {
	WCTextFile *textFile = [tabViewItem identifier];
	if (flag)
		[textFile saveTextFile];
	
	[self unbindTabViewItem:tabViewItem inTabViewContext:context];
}

- (void)removeAllTabViewItemsForTextFile:(WCTextFile *)textFile; {
	for (NSObject <WCTabViewContext> *context in [self allTabViewContexts]) {
		NSTabViewItem *item = [[context tabView] tabViewItemWithIdentifier:textFile];
		if (item)
			[[context tabView] removeTabViewItem:item];
	}
	
	for (WCTextFileWindowController *wc in [self textFileWindowControllers]) {
		if ([textFile isEqualTo:[wc textFile]]) {
			[self removeWindowController:wc];
		}
	}
}

- (void)unbindTabViewItem:(NSTabViewItem *)item inTabViewContext:(NSObject <WCTabViewContext> *)context; {
	WCTextFile *textFile = [item identifier];
	
	[[textFile fileTextStorage] removeLayoutManager:[[[item view] documentView] layoutManager]];
	
	NSUInteger count = [[self openTextFiles] countForObject:textFile];
	
	if (count == 1)
		[textFile resetTextFile];
	
	// descrease the open count for the text file
	[[self openTextFiles] removeObject:textFile];
}

- (void)unbindAllTabViewItemsInTabViewContext:(NSObject <WCTabViewContext> *)context; {	
	for (NSTabViewItem *item in [[context tabView] tabViewItems]) {
		[self unbindTabViewItem:item inTabViewContext:context];
	}
}

- (void)removeAllTabViewItemsInTabViewContext:(NSObject <WCTabViewContext> *)context; {
	for (id identifier in [[[context tabView] tabViewItems] valueForKey:@"identifier"]) {
		[[context tabView] removeTabViewItemWithIdentifier:identifier];
	}
}

- (BOOL)selectTabViewItemWithIdentifier:(WCFile *)file inTabViewContext:(NSObject <WCTabViewContext> *)context; {
	NSTabViewItem *item = [[context tabView] tabViewItemWithIdentifier:file];
	if (!item)
		return NO;
	
	[[context tabView] selectTabViewItem:item];
	return YES;
}
#pragma mark WCTabViewContext Protocol
- (WCTextFile *)currentTextFile; {
	return [[self currentTabViewItem] identifier];
}
- (WCTextView *)currentTextView; {
	return [[[self currentTabViewItem] view] documentView];
}
- (NSTabViewItem *)currentTabViewItem; {
	return [[self tabView] selectedTabViewItem];
}
- (NSUInteger)numberOfTabViewItems; {
	return [[self tabView] numberOfTabViewItems];
}
- (NSTabView *)tabView; {
	return _tabView;
}
- (PSMTabBarControl *)psmTabBarControl; {
	return _psmTabBarControl;
}
- (NSWindow *)contextWindow; {
	return [self projectWindow];
}
#pragma mark Accessors
@synthesize projectGroups=_projectGroups, psmTabBarControl=_psmTabBarControl, projectOutlineView=_projectOutlineView, leftVerticalSplitView=_verticalSplitView, horizontalSplitView=_horizontalSplitView, files=_files, fileDetailTableView=_fileTableView, projectBuildStatusTextField=_projectBuildStatusTextField, projectInfoString=_projectInfoString,errorsWarningsTableView=_errorsAndWarningsTableView,errorsAndWarnings=_errorsAndWarnings,projectLabelsTableView=_currentTextFileSymbolsTableView,currentTextFileSymbols=_currentTextFileSymbols,shouldRecacheTextFiles=_shouldRecacheTextFiles,shouldAllowEmptyFileDetailTableView=_shouldAllowEmptyFileDetailTableView,building=_building,shouldRecacheProjectLabels=_shouldRecacheProjectLabels,shouldRecacheProjectEquates=_shouldRecacheProjectEquates,shouldRecacheProjectDefines=_shouldRecacheProjectDefines,shouldRecacheProjectMacros=_shouldRecacheProjectMacros,shouldIgnoreChanges=_shouldIgnoreChanges,bookmarks=_bookmarks,buildStatusImagesAndStrings=_buildStatusImagesAndStrings,shouldRunAfterBuilding=_shouldRunAfterBuilding,shouldDebugAfterBuilding=_shouldDebugAfterBuilding,projectSearchField=_projectSearchField,shouldRecacheProjectSymbolsArray=_shouldRecacheProjectSymbolsArray,projectSymbolsArray=_projectSymbolsArray,waitingForModalAlertReply=_waitingForModalAlertReply,buildProductsOutputFilePaths=_buildProductsOutputFilePaths;

@dynamic textFileWindowControllers;
- (NSMutableSet *)textFileWindowControllers {
	if (!_textFileWindowControllers) {
		_textFileWindowControllers = [[NSMutableSet alloc] init];
	}
	return _textFileWindowControllers;
}

@dynamic buildProductsGroup;
- (WCBuildProductsGroup *)buildProductsGroup {
	if (!_buildProductsGroup) {
		for (id group in [self projectGroups]) {
			if ([group isKindOfClass:[WCBuildProductsGroup class]]) {
				_buildProductsGroup = group;
				break;
			}
		}
	}
	return _buildProductsGroup;
}

- (NSArray *)projectSymbolsArray {
	if (!_projectSymbolsArray || [self shouldRecacheProjectSymbolsArray]) {
		[self setShouldRecacheProjectSymbolsArray:NO];
		
		NSMutableArray *symbols = [[NSMutableArray alloc] init];
		
		for (WCTextFile *tf in [self textFiles]) {
			[symbols addObjectsFromArray:[tf fileSymbolsArray]];
		}
		
		[self setProjectSymbolsArray:symbols];
	}
	return _projectSymbolsArray;
}

@synthesize activeBuildTarget=_activeBuildTarget,activeBuildConfiguration=_activeBuildConfiguration,buildTask=_buildTask,buildProductsDirectoryPath=_buildProductsDirectoryPath,rootBuildMessage=_rootBuildMessage;

@dynamic activeBuildTarget;
- (WCBuildTarget *)activeBuildTarget {
	if (!_activeBuildTarget) {
		for (WCBuildTarget *target in [[self buildTargetsGroup] childNodes]) {
			if ([target isTargetActive]) {
				_activeBuildTarget = target;
				break;
			}
		}
	}
	return _activeBuildTarget;
}
- (void)setActiveBuildTarget:(WCBuildTarget *)value {
	if (_activeBuildTarget == value)
		return;
	
	_activeBuildTarget = value;
	[[self buildTargetsGroup] setActiveBuildTarget:value];
	
	[[NSNotificationCenter defaultCenter] postNotificationName:kWCProjectActiveBuildTargetDidChangeNotification object:self];
	
	[_projectOutlineView setNeedsDisplay:YES];
}

@dynamic activeBuildConfiguration;
- (WCBuildConfiguration *)activeBuildConfiguration {
	if (!_activeBuildConfiguration) {
		for (WCBuildConfiguration *config in [[self activeBuildTarget] buildConfigurations]) {
			if ([config isConfigurationActive]) {
				_activeBuildConfiguration = config;
				break;
			}
		}
	}
	return _activeBuildConfiguration;
}
- (void)setActiveBuildConfiguration:(WCBuildConfiguration *)value {
	if (_activeBuildConfiguration == value)
		return;
	
	_activeBuildConfiguration = value;
	[[self activeBuildTarget] setActiveBuildConfiguration:value];
	
	[[NSNotificationCenter defaultCenter] postNotificationName:kWCProjectActiveBuildConfigurationDidChangeNotification object:self];
}

@dynamic targets;
- (NSMutableArray *)targets {
	if (!_targets) {
		_targets = [[NSMutableArray alloc] init];
	}
	return _targets;
}

@dynamic steps;
- (NSMutableArray *)steps {
	if (!_steps) {
		_steps = [[NSMutableArray alloc] init];
	}
	return _steps;
}

@dynamic shouldRecacheProjectSymbols;
- (BOOL)shouldRecacheProjectSymbols {
	return (_shouldRecacheProjectLabels && _shouldRecacheProjectEquates && _shouldRecacheProjectDefines && _shouldRecacheProjectMacros);
}
- (void)setShouldRecacheProjectSymbols:(BOOL)flag {
	[self setShouldRecacheProjectDefines:flag];
	[self setShouldRecacheProjectEquates:flag];
	[self setShouldRecacheProjectMacros:flag];
	[self setShouldRecacheProjectLabels:flag];
}

@dynamic currentView;
- (NSView *)currentView {
	return _currentSwapView;
}
- (void)setCurrentView:(NSView *)value {
	if (_currentSwapView == value)
		return;
	
	if (!_currentSwapView) {
		_currentSwapView = value;
		[value setFrameSize:[_swapView frame].size];
		[_swapView addSubview:value];
		return;
	}
	
	[value setFrameSize:[_currentSwapView frame].size];
	[_swapView replaceSubview:_currentSwapView with:value];
	_currentSwapView = value;
}

@dynamic allTabViewContexts;
- (NSArray *)allTabViewContexts {
	NSMutableArray *contexts = [NSMutableArray arrayWithObject:self];
	
	if ([self findInProjectWindowControllerDontCreate])
		[contexts addObject:[self findInProjectWindowControllerDontCreate]];
	if ([self buildResultsWindowControllerDontCreate])
		[contexts addObject:[self buildResultsWindowControllerDontCreate]];
	
	return [[contexts copy] autorelease];
}

@dynamic textFiles;
- (NSMutableSet *)textFiles {
	if (!_textFiles) {
		_textFiles = [[NSMutableSet alloc] init];
		_shouldRecacheTextFiles = YES;
	}
	
	if (_shouldRecacheTextFiles) {
		_shouldRecacheTextFiles = NO;
		[_textFiles removeAllObjects];
		
		for (WCTextFile *tf in [[self projectGroup] textFiles]) {
			[_textFiles addObject:tf];
		}
	}
	return _textFiles;
}

@dynamic projectLabels;
- (NSSet *)projectLabels {
	if (!_projectLabels) {
		_projectLabels = [[NSMutableSet alloc] init];
		_shouldRecacheProjectLabels = YES;
	}
	
	if (_shouldRecacheProjectLabels) {
		_shouldRecacheProjectLabels = NO;
		[_projectLabels removeAllObjects];
		BOOL labelsAreCaseSensitive = [[[[self buildTargetsGroup] activeBuildTarget] activeBuildConfiguration] labelsAndMacrosAreCaseSensitive];
		
		for (WCTextFile *tf in [self textFiles]) {
			[_projectLabels unionSet:(labelsAreCaseSensitive)?[tf fileLabels]:[[tf fileLabels] valueForKey:@"lowercaseString"]];
		}
	}
	return [[_projectLabels copy] autorelease];
}

@dynamic projectEquates;
- (NSSet *)projectEquates {
	if (!_projectEquates) {
		_projectEquates = [[NSMutableSet alloc] init];
		_shouldRecacheProjectEquates = YES;
	}
	
	if (_shouldRecacheProjectEquates) {
		_shouldRecacheProjectEquates = NO;
		[_projectEquates removeAllObjects];
		BOOL labelsAreCaseSensitive = [[[[self buildTargetsGroup] activeBuildTarget] activeBuildConfiguration] labelsAndMacrosAreCaseSensitive];
		
		for (WCTextFile *tf in [self textFiles]) {
			[_projectEquates unionSet:(labelsAreCaseSensitive)?[tf fileEquates]:[[tf fileEquates] valueForKey:@"lowercaseString"]];
		}
	}
	
	return [[_projectEquates copy] autorelease];
}

@dynamic projectDefines;
- (NSSet *)projectDefines {
	if (!_projectDefines) {
		_projectDefines = [[NSMutableSet alloc] init];
		_shouldRecacheProjectDefines = YES;
	}
	
	if (_shouldRecacheProjectDefines) {
		_shouldRecacheProjectDefines = NO;
		[_projectDefines removeAllObjects];
		BOOL labelsAreCaseSensitive = [[[[self buildTargetsGroup] activeBuildTarget] activeBuildConfiguration] labelsAndMacrosAreCaseSensitive];
		
		for (WCTextFile *tf in [self textFiles]) {
			[_projectDefines unionSet:(labelsAreCaseSensitive)?[tf fileDefines]:[[tf fileDefines] valueForKey:@"lowercaseString"]];
		}
	}
	return [[_projectDefines copy] autorelease];
}

@dynamic projectMacros;
- (NSSet *)projectMacros {
	if (!_projectMacros) {
		_projectMacros = [[NSMutableSet alloc] init];
		_shouldRecacheProjectMacros = YES;
	}
	
	if (_shouldRecacheProjectMacros) {
		_shouldRecacheProjectMacros = NO;
		[_projectMacros removeAllObjects];
		BOOL labelsAreCaseSensitive = [[[[self buildTargetsGroup] activeBuildTarget] activeBuildConfiguration] labelsAndMacrosAreCaseSensitive];
		
		for (WCTextFile *tf in [self textFiles]) {
			[_projectMacros unionSet:(labelsAreCaseSensitive)?[tf fileMacros]:[[tf fileMacros] valueForKey:@"lowercaseString"]];
		}
	}
	return [[_projectMacros copy] autorelease];
}

@dynamic projectSymbolsForAutocomplete;
- (NSSet *)projectSymbolsForAutocomplete {
	NSMutableSet *symbols = [NSMutableSet set];
	
	[symbols unionSet:[self projectLabels]];
	[symbols unionSet:[self projectEquates]];
	
	return [[symbols copy] autorelease];
}

@dynamic currentTabViewContext;
- (NSObject <WCTabViewContext> *)currentTabViewContext; {
	id wc = [[NSApp keyWindow] windowController];
	if ([wc conformsToProtocol:@protocol(WCTabViewContext)])
		return wc;
	else if ([[NSApp keyWindow] isEqualTo:[self projectWindow]])
		return self;
	return nil;
}

@dynamic absoluteFilePathOfFirstTextFile;
- (NSString *)absoluteFilePathOfFirstTextFile {
	for (id node in [[self projectGroup] descendantLeafNodes]) {
		if ([node isKindOfClass:[WCTextFile class]])
			return [node absoluteFilePath];
	}
	return nil;
}

@dynamic projectGroup;
- (WCProjectGroup *)projectGroup {
	if (!_projectGroup) {
		for (id group in [self projectGroups]) {
			if ([group isKindOfClass:[WCProjectGroup class]]) {
				_projectGroup = group;
				break;
			}
		}
	}
	return _projectGroup;
}

@dynamic buildTargetsGroup;
- (WCBuildTargetsGroup *)buildTargetsGroup {
	if (!_buildTargetsGroup) {
		for (id group in [self projectGroups]) {
			if ([group isKindOfClass:[WCBuildTargetsGroup class]]) {
				_buildTargetsGroup = group;
				break;
			}
		}
	}
	return _buildTargetsGroup;
}

@dynamic errorsAndWarningsGroup;
- (WCErrorsAndWarningsGroup *)errorsAndWarningsGroup {
	if (!_errorsAndWarningsGroup) {
		for (id group in [self projectGroups]) {
			if ([group isKindOfClass:[WCErrorsAndWarningsGroup class]]) {
				_errorsAndWarningsGroup = group;
				break;
			}
		}
	}
	return _errorsAndWarningsGroup;
}

@dynamic bookmarksGroup;
- (WCBookmarksGroup *)bookmarksGroup {
	if (!_bookmarksGroup) {
		for (id group in [self projectGroups]) {
			if ([group isKindOfClass:[WCBookmarksGroup class]]) {
				_bookmarksGroup = group;
				break;
			}
		}
	}
	return _bookmarksGroup;
}

@dynamic insertionIndexForBuildTargetsGroup;
- (NSUInteger)insertionIndexForBuildTargetsGroup {
	NSTreeNode *node = [[self projectTreeController] selectedNode];
	
	if ([[node representedObject] isKindOfClass:[WCBuildTarget class]])
		return [[node indexPath] lastIndex];
	else if ([[node representedObject] isKindOfClass:[WCBuildConfiguration class]])
		return [[[node parentNode] indexPath] lastIndex];
	return 0;
}

@dynamic selectedFileNode;
- (NSTreeNode *)selectedFileNode {
	NSArray *selectedNodes = [self selectedNodes];
	
	if ([selectedNodes count] &&
		[[[selectedNodes firstObject] representedObject] isKindOfClass:[WCFile class]])
		return [selectedNodes firstObject];
	return [[self projectTreeController] treeNodeForRepresentedObject:[self projectGroup]];
}

@dynamic selectedBuildConfigurationNode;
- (NSTreeNode *)selectedBuildConfigurationNode {
	NSTreeNode *selectedNode = [[self projectTreeController] selectedNode];
	
	if ([[selectedNode representedObject] isKindOfClass:[WCBuildConfiguration class]] ||
		[[selectedNode representedObject] isKindOfClass:[WCBuildStep class]])
		return selectedNode;
	return [[self projectTreeController] treeNodeForRepresentedObject:[[[self buildTargetsGroup] activeBuildTarget] activeBuildConfiguration]];
}

@dynamic selectedNodes;
// this method always returns an array of NSTreeNode objects
- (NSArray *)selectedNodes {
	if ([[[self projectWindow] firstResponder] isKindOfClass:[WCKeyDownTableView class]]) {
		if ([[(id)[[self projectWindow] firstResponder] dataSource] isEqualTo:[_fileTableView dataSource]])
			return [[self projectTreeController] treeNodesForRepresentedObjects:[[self fileDetailArrayController] selectedObjects]];
		else if ([[(id)[[self projectWindow] firstResponder] dataSource] isEqualTo:[_bookmarksTableView dataSource]])
			return [[self projectTreeController] treeNodesForRepresentedObjects:[[_bookmarksTableView dataSource] selectedObjects]];
	}
	return [[self projectTreeController] selectedNodes];
}

@dynamic selectedRepresentedObjects;
- (NSArray *)selectedRepresentedObjects {
	return [[self selectedNodes] valueForKey:@"representedObject"];
}

@dynamic projectPreferencesPath;
- (NSString *)projectPreferencesPath {
	NSString *wrapperPath = [[self fileURL] path];
	return [wrapperPath stringByAppendingPathComponent:[NSUserName() stringByAppendingPathExtension:@"wcodeprefs"]];
}

@dynamic projectDataPath;
- (NSString *)projectDataPath {
	NSString *wrapperPath = [[self fileURL] path];
	return [wrapperPath stringByAppendingPathComponent:[[[wrapperPath lastPathComponent] stringByDeletingPathExtension] stringByAppendingPathExtension:@"wcodedata"]];
}

@dynamic projectPreferences;
- (NSMutableDictionary *)projectPreferences {
	return _projectPreferences;
}
- (void)setProjectPreferences:(NSMutableDictionary *)value {
	if (_projectPreferences == value)
		return;
	
	[_projectPreferences release];
	_projectPreferences = [value retain];
	
	[self _loadProjectPreferences];
}

@dynamic projectWindow;
- (NSWindow *)projectWindow {
	for (id controller in [self windowControllers])
		if ([controller isKindOfClass:[WCProjectWindowController class]])
			return [controller window];
	return nil;
}

@dynamic projectTreeController;
- (NSTreeController *)projectTreeController {
	return [[self projectOutlineView] dataSource];
}

@dynamic fileDetailArrayController;
- (NSArrayController *)fileDetailArrayController {
	return [[self fileDetailTableView] dataSource];
}

@dynamic projectName;
- (NSString *)projectName {
	return [[self displayName] stringByDeletingPathExtension];
}

@dynamic projectFolderPath;
- (NSString *)projectFolderPath {
	return [[[self fileURL] path] stringByDeletingLastPathComponent];
}

@dynamic unsavedTextFiles;
- (NSArray *)unsavedTextFiles {
	NSMutableArray *files = [NSMutableArray array];
	
	for (WCTextFile *file in [[self projectGroup] textFiles]) {
		if ([file isEdited]) {
			[files addObject:file];
		}
	}
	return [[files copy] autorelease];
}

@dynamic openTextFiles;
- (NSCountedSet *)openTextFiles {
	if (!_openTextFiles) {
		_openTextFiles = [[NSCountedSet alloc] init];
	}
	return _openTextFiles;
}

@dynamic insertionIndexForIncludeDirectoryGroup;
- (NSUInteger)insertionIndexForIncludeDirectoryGroup {
	NSTreeNode *node = [[self projectTreeController] selectedNode];
	
	if ([[node representedObject] isKindOfClass:[WCIncludeDirectory class]])
		return [[node indexPath] lastIndex];
	return 0;
}

@dynamic findInProjectWindowController;
- (WCFindInProjectWindowController *)findInProjectWindowController {
	if (!_findInProjectWindowController) {
		_findInProjectWindowController = [[WCFindInProjectWindowController alloc] init];
		
		[_findInProjectWindowController setDocument:self];
	}
	return _findInProjectWindowController;
}

@dynamic findInProjectWindowControllerDontCreate;
- (WCFindInProjectWindowController *)findInProjectWindowControllerDontCreate {
	return _findInProjectWindowController;
}

@dynamic buildResultsWindowController;
- (WCBuildResultsWindowController *)buildResultsWindowController {
	if (!_buildResultsWindowController) {
		_buildResultsWindowController = [[WCBuildResultsWindowController alloc] init];
		[_buildResultsWindowController setDocument:self];
	}
	return _buildResultsWindowController;
}

@dynamic buildResultsWindowControllerDontCreate;
- (WCBuildResultsWindowController *)buildResultsWindowControllerDontCreate {
	return _buildResultsWindowController;
}

@dynamic buildTargetGetInfoWindowControllers;
- (NSMutableSet *)buildTargetGetInfoWindowControllers {
	if (!_buildTargetGetInfoWindowControllers) {
		_buildTargetGetInfoWindowControllers = [[NSMutableSet alloc] init];
	}
	return _buildTargetGetInfoWindowControllers;
}

@dynamic fileGetInfoWindowControllers;
- (NSMutableSet *)fileGetInfoWindowControllers {
	if (!_fileGetInfoWindowControllers) {
		_fileGetInfoWindowControllers = [[NSMutableSet alloc] init];
	}
	return _fileGetInfoWindowControllers;
}
#pragma mark IBActions
- (IBAction)openInSeparateEditorAction:(id)sender; {
	if ([sender isKindOfClass:[WCTextView class]]) {
		[self createSeparateEditorForTextFile:[sender textFile]];
		return;
	}
	
	for (id object in [self selectedRepresentedObjects]) {
		if ([object isKindOfClass:[WCTextFile class]]) {
			[self createSeparateEditorForTextFile:object];
		}
	}
}

- (IBAction)buildProductsAction:(id)sender; {
	[[[self projectOutlineView] dataSource] selectRepresentedObject:[self buildProductsGroup]];
	[[self projectOutlineView] expandItem:[[self projectOutlineView] itemAtRow:[[self projectOutlineView] selectedRow]]];
}

- (IBAction)searchProjectAction:(id)sender; {
	[[self projectWindow] makeFirstResponder:_projectSearchField];
}

- (IBAction)renameAction:(id)sender; {
	if ([[[self projectWindow] firstResponder] isKindOfClass:[NSOutlineView class]])
		[[self projectOutlineView] editColumn:[[self projectOutlineView] columnWithIdentifier:kWCProjectOutlineViewGroupsAndFilesColumnIdentifier] row:[[self projectOutlineView] selectedRow] withEvent:nil select:YES];
	else
		[[self fileDetailTableView] editColumn:[[self fileDetailTableView] columnWithIdentifier:kWCProjectFileTableViewNameColumnIdentifier] row:[[self fileDetailTableView] selectedRow] withEvent:nil select:YES];
}

- (IBAction)addToProjectAction:(id)sender; {
	[WCAddToProjectSheetController presentAddToProjectSheetForProject:self];
}

- (IBAction)newGroupAction:(id)sender; {
	NSTreeNode *node = [self selectedFileNode];
	NSUInteger index = ([node isLeaf])?[[node indexPath] lastIndex]:0;
	
	if ([node isLeaf])
		node = [node parentNode];
	
	WCGroup *group = [WCGroup groupWithFilePath:[[node representedObject] folderPath] name:NSLocalizedString(@"New Group",@"New Group") inProject:self];
	
	[[[node representedObject] mutableChildNodes] insertObject:group atIndex:index];
	[[self projectTreeController] selectRepresentedObject:group];
	[[self projectWindow] makeFirstResponder:[self projectOutlineView]];
	[self renameAction:nil];
}

- (IBAction)delete:(id)sender; {
	//[[self projectTreeController] removeSelectedObjects];
	NSArray *selectedObjects = [self selectedRepresentedObjects];
	id firstObject = [selectedObjects firstObject];
	NSAlert *alert = nil;	
	
	// we are deleting files and groups
	if ([firstObject isKindOfClass:[WCFile class]]) {
		// if all the selected files are invalid (colored red) or empty groups we can delete them and be done
		BOOL allInvalidOrEmptyFiles = YES;
		for (WCFile *file in selectedObjects) {
			// a file is valid, confirm the delete with the user
			if ([file isLeaf] && [file isValid]) {
				allInvalidOrEmptyFiles = NO;
				break;
			}
			// a group isn't empty, check all of its children
			else if (![file isLeaf] && [[file childNodes] count]) {
				BOOL allChildrenInvalidOrEmpty = YES;
				for (WCFile *sFile in [file descendantNodes]) {
					if ([sFile isLeaf] && [file isValid]) {
						allChildrenInvalidOrEmpty = NO;
						break;
					}
				}
				
				if (!allChildrenInvalidOrEmpty) {
					allInvalidOrEmptyFiles = NO;
					break;
				}
			}
		}
		
		if (allInvalidOrEmptyFiles) {
			[self deleteObjects:selectedObjects moveFilesToTrash:NO];
			return;
		}
		
		if ([selectedObjects count] == 1) {
			alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Delete File Named \"%@\"",@"Delete File Named \"%@\""), [[selectedObjects firstObject] name]] defaultButton:NSLocalizedString(@"Delete Reference",@"Delete Reference") alternateButton:NSLocalizedString(@"Also Move To Trash",@"Also Move To Trash") otherButton:NSLocalizedString(@"Cancel",@"Cancel") informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"Do you want to delete the reference to \"%@\" from the project, or also move the file to the trash?",@"Format text for delete file alert"), [[selectedObjects firstObject] name]]];
		}
		else {
			alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Delete %u Files",@"Delete %u Files"), [selectedObjects count]] defaultButton:NSLocalizedString(@"Delete References",@"Delete References") alternateButton:NSLocalizedString(@"Also Move To Trash",@"Also Move To Trash") otherButton:NSLocalizedString(@"Cancel",@"Cancel") informativeTextWithFormat:[NSString stringWithFormat:NSLocalizedString(@"Do you want to remove the references to %u files from the project, or also move the files to the trash?",@"Format text for delete multiple files alert"), [selectedObjects count]]];
		}
		
		[alert setAlertStyle:NSCriticalAlertStyle];
		
		[alert beginSheetModalForWindow:[self projectWindow] modalDelegate:self didEndSelector:@selector(_deleteFilesAlertDidEnd:code:info:) contextInfo:NULL];
		return;
	}
	// the user wants to delete build targets
	else if ([firstObject isKindOfClass:[WCBuildTarget class]]) {
		
		if ([selectedObjects count] == 1) {
			alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Delete Build Target \"%@\"",@"Delete Build Target \"%@\""), [firstObject name]] defaultButton:NSLocalizedString(@"Delete Target",@"Delete Target") alternateButton:NSLocalizedButtonCancel otherButton:nil informativeTextWithFormat:NSLocalizedString(@"Deleting this build target will also delete its build configurations and associated build steps. Are you sure you want to delete this build target?",@"Format text for delete build target alert")];
		}
		else {
			alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Delete %u Build Targets",@"Delete %u Build Targets"), [selectedObjects count]] defaultButton:NSLocalizedString(@"Delete Targets",@"Delete Targets") alternateButton:NSLocalizedButtonCancel otherButton:nil informativeTextWithFormat:NSLocalizedString(@"Deleting build targets will also delete their build configurations and associated build steps. Are you sure you want to delete these build targets?",@"Format text for delete build targets alert")];
		}
		
		[alert setAlertStyle:NSCriticalAlertStyle];
	}
	else if ([firstObject isKindOfClass:[WCBuildStep class]]) {
		if ([selectedObjects count] == 1) {
			alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Delete Build Step \"%@\"",@"Delete Build Step \"%@\""), [firstObject name]] defaultButton:NSLocalizedString(@"Delete Step",@"Delete Step") alternateButton:NSLocalizedButtonCancel otherButton:nil informativeTextWithFormat:NSLocalizedString(@"Deleting this build step will prevent its associated ouput from being generated during the build process. Are you sure you want to delete this build step?",@"Format string for delete build step alert")];
		}
		else {
			alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Delete %u Build Configurations",@"Delete %u Build Configurations"), [selectedObjects count]] defaultButton:NSLocalizedString(@"Delete Steps",@"Delete Steps") alternateButton:NSLocalizedButtonCancel otherButton:nil informativeTextWithFormat:NSLocalizedString(@"Deleting these build steps will prevent their associated output from being generated during the build process. Are you sure you want to delete these build steps?",@"Format string for delete build steps alert")];
		}
		
		[alert setAlertStyle:NSCriticalAlertStyle];
	}
	else if ([firstObject isKindOfClass:[WCBookmark class]]) {
		if ([selectedObjects count] == 1) {
			alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Delete Bookmark",@"Delete Bookmark") defaultButton:NSLocalizedString(@"Delete",@"Delete") alternateButton:NSLocalizedButtonCancel otherButton:nil informativeTextWithFormat:NSLocalizedString(@"Are you sure you want to delete this bookmark?",@"Format string for delete bookmark alert")];
		}
		else {
			alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Delete %u Bookmarks",@"Delete %u Bookmarks"), [selectedObjects count]] defaultButton:NSLocalizedString(@"Delete",@"Delete") alternateButton:NSLocalizedButtonCancel otherButton:nil informativeTextWithFormat:NSLocalizedString(@"Are you sure you want to delete these bookmarks?",@"Format string for delete bookmarks alert")];
		}
		
		[alert setAlertStyle:NSCriticalAlertStyle];
	}
	
	[alert beginSheetModalForWindow:[self windowForSheet] modalDelegate:self didEndSelector:@selector(_deleteAlertDidEnd:code:info:) contextInfo:NULL];
}

- (IBAction)groupAction:(id)sender; {
	NSArray *selectedNodes = [self selectedNodes];
	NSTreeNode *node = [selectedNodes firstObject];
	NSUInteger index = ([node isLeaf])?[[node indexPath] lastIndex]:0;
	
	if ([node isLeaf])
		node = [node parentNode];
	
	WCGroup *group = [WCGroup groupWithFilePath:[[node representedObject] folderPath] name:NSLocalizedString(@"New Group",@"New Group") inProject:self];
	
	[[[node representedObject] mutableChildNodes] insertObject:group atIndex:index];
	
	NSTreeNode *newNode = [[self projectTreeController] treeNodeForRepresentedObject:group];
	
	[[self projectTreeController] moveNodes:selectedNodes toIndexPath:[[newNode indexPath] indexPathByAddingIndex:0]];
	[[self projectOutlineView] collapseItem:newNode];
	[[self projectTreeController] selectTreeNode:newNode];
	[[self projectWindow] makeFirstResponder:[self projectOutlineView]];
	[self renameAction:nil];
}

- (IBAction)ungroupAction:(id)sender; {
	NSArray *nodes = [self selectedNodes];
	NSMutableArray *representedObjectsToSelect = [NSMutableArray array];
	
	for (NSTreeNode *node in nodes) {
		NSArray *representedChildNodes = [[node childNodes] valueForKey:@"representedObject"];
		
		[representedObjectsToSelect addObjectsFromArray:representedChildNodes];
		
		[[[[node parentNode] representedObject] mutableChildNodes] insertObjects:representedChildNodes atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange([[node indexPath] lastIndex], [representedChildNodes count])]];
	}
	
	[[self projectTreeController] removeTreeNodes:nodes];
	[[self projectTreeController] selectRepresentedObjects:representedObjectsToSelect];
}

- (IBAction)openWithFinderAction:(id)sender; {
	NSArray *selectedObjects = [self selectedRepresentedObjects];
	for (id object in selectedObjects) {
		if ([object isMemberOfClass:[WCTextFile class]]) {
			[self addTabViewItemForTextFile:object inTabViewContext:self];
		}
		else if ([object isMemberOfClass:[WCFile class]]) {
			[[NSWorkspace sharedWorkspace] openFile:[object absoluteFilePath]];
		}
		else if ([object isMemberOfClass:[WCBuildMessage class]]) {
			[self _showErrorOrWarningForBuildMessage:object];
		}
		else if ([object isMemberOfClass:[WCBookmark class]]) {
			[self _jumpToBookmark:object];
		}
	}
}

- (IBAction)revealInFinderAction:(id)sender; {
	for (WCFile *file in [self selectedRepresentedObjects]) {
		[sw selectFile:[file absoluteFilePath] inFileViewerRootedAtPath:[file folderPath]];
	}
}

- (IBAction)getInfoAction:(id)sender; {
	id node = [[self selectedRepresentedObjects] firstObject];
	
	[self showGetInfoWindowForItem:node];
}

- (IBAction)projectAction:(id)sender; {
	[[self projectWindow] makeKeyAndOrderFront:nil];
}

- (IBAction)newBuildConfigurationAction:(id)sender; {
	id object = [[[_projectOutlineView dataSource] selectedRepresentedObjects] firstObject];
	
	if ([object isKindOfClass:[WCBuildTarget class]] ||
		[object isKindOfClass:[WCBuildStep class]]) {
		WCBuildTargetGetInfoWindowController *wc = [self showGetInfoWindowForItem:object];
		[[wc tabView] selectTabViewItemWithIdentifier:kWCBuildTargetGetInfoWindowConfigurationsTabIdentifier];
		[wc addBuildConfigurationAction:nil];
	}
	else {
		WCBuildTargetGetInfoWindowController *wc = [self showGetInfoWindowForItem:[self activeBuildTarget]];
		[[wc tabView] selectTabViewItemWithIdentifier:kWCBuildTargetGetInfoWindowConfigurationsTabIdentifier];
		[wc addBuildConfigurationAction:nil];
	}
}

- (IBAction)newBuildStepAction:(id)sender; {
	id object = [[[_projectOutlineView dataSource] selectedRepresentedObjects] firstObject];
	
	if ([object isKindOfClass:[WCBuildTarget class]] ||
		[object isKindOfClass:[WCBuildStep class]]) {
		WCBuildTargetGetInfoWindowController *wc = [self showGetInfoWindowForItem:object];
		[[wc tabView] selectTabViewItemWithIdentifier:kWCBuildTargetGetInfoWindowBuildStepsTabIdentifier];
		[wc addBuildStepAction:nil];
	}
	else {
		WCBuildTargetGetInfoWindowController *wc = [self showGetInfoWindowForItem:[self activeBuildTarget]];
		[[wc tabView] selectTabViewItemWithIdentifier:kWCBuildTargetGetInfoWindowBuildStepsTabIdentifier];
		[wc addBuildStepAction:nil];
	}
}

- (IBAction)buildAction:(id)sender; {	
	// check to see if there are any unsaved files
	NSArray *unsavedTextFiles = [self unsavedTextFiles];
	if ([unsavedTextFiles count]) {
		WCPBForUnsavedFiles unsavedTextFilesAction = NSUserDefaultsUnsignedIntegerForKey(kWCPrefsBuildingForUnsavedFilesKey);
		
		switch (unsavedTextFilesAction) {
			case WCPBForUnsavedFilesAskBeforeBuilding: {
				NSUInteger result = [WCUnsavedFilesWindowController runModalForUnsavedFiles:unsavedTextFiles type:WCUnsavedFilesTypeBeforeBuilding];
				if (result == NSCancelButton) {
					[self setBuildStatusImagesAndStrings:[NSArray arrayWithObjects:[NSImage imageNamed:@"Build16x16"], NSLocalizedString(@"Cancelled",@"Cancelled"), nil]];
					return;
				}
			}
				break;
			case WCPBForUnsavedFilesAlwaysSave:
				[unsavedTextFiles makeObjectsPerformSelector:@selector(saveTextFile)];
				break;
			case WCPBForUnsavedFilesCancelBuild:
				return;
			case WCPBForUnsavedFilesNeverSave:
			default:
				break;
		}
	}
	
	// there must be an active target
	WCBuildTarget *activeBuildTarget = [self activeBuildTarget];
	if (!activeBuildTarget) {
#ifdef WCDEBUG
		NSLog(@"no active build target");
#endif
		NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"No Active Build Target",@"No Active Build Target") defaultButton:[NSString stringWithFormat:NSLocalizedString(@"Add Target%C",@"Add Target with ellipsis"), kUnicodeCharacterEllipsis] alternateButton:NSLocalizedButtonOK otherButton:nil informativeTextWithFormat:NSLocalizedString(@"The project must have at least one build target defined before it can be built. Would you like to add a build target now?", @"no active build target alert format")];
		
		if ([alert runModal] == NSAlertDefaultReturn)
			[[WCNewTargetWindowController sharedInstance] presentNewTargetWindow];

		return;
	}
	
	// there must an active configuration
	WCBuildConfiguration *activeBuildConfiguration = [self activeBuildConfiguration];
	if (!activeBuildConfiguration) {
#ifdef WCDEBUG
		NSLog(@"no active build configuration");
#endif
		
		NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"No Active Build Configuration",@"No Active Build Configuration") defaultButton:[NSString stringWithFormat:NSLocalizedString(@"Add Configuration%C",@"Add Configuration with ellipsis"), kUnicodeCharacterEllipsis] alternateButton:NSLocalizedButtonOK otherButton:nil informativeTextWithFormat:NSLocalizedString(@"The project must have at least one build configuration defined before it can be built. Would you like to add a build configuration now?", @"no active build configuration alert format")];
		
		if ([alert runModal] == NSAlertDefaultReturn) {
			WCBuildTargetGetInfoWindowController *wc = [self showGetInfoWindowForItem:activeBuildTarget];
			
			[[wc tabView] selectTabViewItemWithIdentifier:kWCBuildTargetGetInfoWindowConfigurationsTabIdentifier];
			[wc addBuildConfigurationAction:nil];
		}
		
		return;
	}
	
	// there must be at least one build step in the active target
	if (![activeBuildTarget numberOfBuildSteps]) {
#ifdef WCDEBUG
		NSLog(@"no build steps for active build target");
#endif
		
		NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"No Build Steps in Active Target",@"No Build Steps in Active Target") defaultButton:[NSString stringWithFormat:NSLocalizedString(@"Add Step%C",@"Add Step with ellipsis"), kUnicodeCharacterEllipsis] alternateButton:NSLocalizedButtonOK otherButton:nil informativeTextWithFormat:NSLocalizedString(@"The active target for the project must have at least one build step defined before the project can be built. Would you like to add a build step now?", @"no build step alert format")];
		
		if ([alert runModal] == NSAlertDefaultReturn) {
			WCBuildTargetGetInfoWindowController *wc = [self showGetInfoWindowForItem:activeBuildTarget];
			
			[[wc tabView] selectTabViewItemWithIdentifier:kWCBuildTargetGetInfoWindowBuildStepsTabIdentifier];
			[wc addBuildStepAction:nil];
		}
		
		return;
	}
	
	// check on the output folder for build products
	NSString *buildProductsDirectoryPath = nil;
	WCPBPlaceBuildProductsIn placeBuildProductsInOption = NSUserDefaultsUnsignedIntegerForKey(kWCPrefsBuildingPlaceBuildProductsInKey);
	BOOL isDirectory = NO;
	switch (placeBuildProductsInOption) {
		case WCPBPlaceBuildProductsInProjectDirectory:
			buildProductsDirectoryPath = [[[self projectFolderPath] stringByAppendingPathComponent:@"build"] stringByAppendingPathComponent:[activeBuildConfiguration name]];
			break;
		case WCPBPlaceBuildProductsInCustomDirectory:
			buildProductsDirectoryPath = [[NSUserDefaultsObjectForKey(kWCPrefsBuildingProductsCustomDirectoryKey) stringByAppendingPathComponent:[self projectName]] stringByAppendingPathComponent:[activeBuildConfiguration name]];
			break;
		default:
			break;
	}
	
	if (!buildProductsDirectoryPath) {
#ifdef WCDEBUG
		NSLog(@"build products directory path not set");
#endif
		return;
	}
	else if (![[NSFileManager defaultManager] fileExistsAtPath:buildProductsDirectoryPath isDirectory:&isDirectory] || !isDirectory) {
		NSError *error = nil;
		if (![[NSFileManager defaultManager] createDirectoryAtPath:buildProductsDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error]) {
#ifdef WCDEBUG
			NSLog(@"unable to create build products directory");
#endif
			return;
		}
	}
	
	[self setBuildProductsDirectoryPath:buildProductsDirectoryPath];
	
	// add our build targets
	[[self targets] addObject:activeBuildTarget];
	// add the build steps for the first target
	[[self steps] addObjectsFromArray:[[[self targets] firstObject] childNodes]];
	
	WCPBBuildResultsWindowOpenDuringBuilds openBuildResultsWindowDuringBuilds = NSUserDefaultsUnsignedIntegerForKey(kWCPrefsBuildingBuildResultsWindowOpenDuringBuilds);
	switch (openBuildResultsWindowDuringBuilds) {
		case WCPBBuildResultsWindowOpenDuringBuildsAlways:
			[self buildResultsAction:nil];
			break;
		case WCPBBuildResultsWindowOpenDuringBuildsNever:
		default:
			break;
	}
	
	WCPBBuildResultsWindowCloseAfterBuilds closeBuildResultsWindowAfterBuilds = NSUserDefaultsUnsignedIntegerForKey(kWCPrefsBuildingErrorsAndWarningsGroupOpenDuringBuildsKey);
	switch (closeBuildResultsWindowAfterBuilds) {
		case WCPBErrorsAndWarningsGroupOpenDuringBuildsAlways:
			[[self projectTreeController] selectRepresentedObject:[self errorsAndWarningsGroup]];
			[[self projectOutlineView] expandItem:[[self projectOutlineView] itemAtRow:[[self projectOutlineView] selectedRow]]];
			break;
		case WCPBErrorsAndWarningsGroupOpenDuringBuildsNever:
		default:
			break;
	}
	
	[[[self buildProductsGroup] mutableChildNodes] removeAllObjects];
	[self setProjectInfoString:[NSString stringWithFormat:NSLocalizedString(@"Building%C",@"Building with ellipsis"), kUnicodeCharacterEllipsis]];
	[self _buildNextStep];
}

- (IBAction)buildAndRunAction:(id)sender; {
	[self setShouldRunAfterBuilding:YES];
	[self buildAction:nil];
}

- (IBAction)buildAndDebugAction:(id)sender; {
	[self setShouldDebugAfterBuilding:YES];
	[self buildAction:nil];
}

- (IBAction)buildResultsAction:(id)sender; {
	[[self buildResultsWindowController] showWindow:nil];
}

- (IBAction)consoleOutputAction:(id)sender; {
	
}

- (IBAction)fileDetailAction:(id)sender; {
	[[self projectWindow] makeFirstResponder:_fileTableView];
}

- (IBAction)cleanAction:(id)sender; {
	
}

- (IBAction)cleanAllAction:(id)sender; {
	
}

- (IBAction)stopAction:(id)sender; {
	
}

- (IBAction)nextTabAction:(id)sender; {
	PSMTabBarControl *tabBarControl = [[self currentTabViewContext] psmTabBarControl];
	NSArray *tabs = [tabBarControl representedTabViewItems];
	NSTabViewItem *item = [[tabBarControl tabView] selectedTabViewItem];
	
	if ([item isEqualTo:[tabs lastObject]])
		[[tabBarControl tabView] selectTabViewItem:[tabs firstObject]];
	else
		[[tabBarControl tabView] selectTabViewItem:[tabs objectAtIndex:[tabs indexOfObject:item]+1]];
}

- (IBAction)previousTabAction:(id)sender; {
	PSMTabBarControl *tabBarControl = [[self currentTabViewContext] psmTabBarControl];
	NSArray *tabs = [tabBarControl representedTabViewItems];
	NSTabViewItem *item = [[tabBarControl tabView] selectedTabViewItem];
	
	if ([item isEqualTo:[tabs firstObject]])
		[[tabBarControl tabView] selectTabViewItem:[tabs lastObject]];
	else
		[[tabBarControl tabView] selectTabViewItem:[tabs objectAtIndex:[tabs indexOfObject:item]-1]];
}

- (IBAction)revealInGroupTreeAction:(id)sender; {
	if ([sender isKindOfClass:[WCTextView class]]) {
		[[self projectTreeController] selectRepresentedObject:[sender textFile]];
	}
	else {
		[[self projectTreeController] selectRepresentedObject:[[[self fileDetailArrayController] selectedObjects] firstObject]];
	}
	[[self projectWindow] makeKeyAndOrderFront:nil];
	[[self projectWindow] makeFirstResponder:[self projectOutlineView]];
}

- (IBAction)findInProjectAction:(id)sender; {
	[[self findInProjectWindowController] showWindow:nil];
}

- (IBAction)splitEditorAction:(id)sender; {
	// TODO: this still needs work
	NSTabViewItem *tabViewItem = [self currentTabViewItem];
	// this will be the first subview of our split view
	NSScrollView *currentScrollView = [[[tabViewItem view] retain] autorelease];
	// create a new split view to hold the two text views
	WCProjectSplitView *splitView = [[[WCProjectSplitView  alloc] initWithFrame:[currentScrollView frame]] autorelease];
	[splitView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
	[tabViewItem setView:splitView];
	
	NSScrollView *newScrollView = [[WCInterfacePerformer sharedInstance] scrollViewWithTextViewForTextFile:[[currentScrollView documentView] textFile] inProject:self];
	
	[splitView addSubview:currentScrollView];
	[splitView addSubview:newScrollView];
	NSRect frame = [newScrollView frame];
	frame.origin.y += 1.0;
	frame.size.height -= 1.0;
	[newScrollView setFrame:frame];
	
	[splitView adjustSubviews];
	[splitView setPosition:floor(NSHeight([splitView frame])/2) ofDividerAtIndex:0];
}

- (IBAction)searchAction:(id)sender; {
	NSString *searchString = [sender stringValue];
	BOOL isEmpty = (!searchString || [searchString isEqualToString:@""]);
	
	if (_currentSwapView == _fileView) {
		if (isEmpty)
			[[_fileTableView dataSource] setFilterPredicate:nil];
		else
			[[_fileTableView dataSource] setFilterPredicate:[NSPredicate predicateWithFormat:@"name contains[cd] %@", searchString]];
	}
	else if (_currentSwapView == _errorsAndWarningsView) {
		if (isEmpty)
			[[_errorsAndWarningsTableView dataSource] setFilterPredicate:nil];
		else
			[[_errorsAndWarningsTableView dataSource] setFilterPredicate:[NSPredicate predicateWithFormat:@"messageLocation contains[cd] %@", searchString]];
	}
	else if (_currentSwapView == _bookmarksView) {
		if (isEmpty)
			[[_bookmarksTableView dataSource] setFilterPredicate:nil];
		else
			[[_bookmarksTableView dataSource] setFilterPredicate:[NSPredicate predicateWithFormat:@"name contains[cd] %@", searchString]];
	}
	else if (_currentSwapView == _projectSymbolsView) {
		if (isEmpty)
			[[_projectSymbolsTableView dataSource] setFilterPredicate:nil];
		else
			[[_projectSymbolsTableView dataSource] setFilterPredicate:[NSPredicate predicateWithFormat:@"name contains[cd] %@", searchString]];
	}
}

- (IBAction)makeActiveTargetAction:(id)sender; {
	[[self buildTargetsGroup] setActiveBuildTarget:[[[[self projectTreeController] selectedNodes] objectAtIndex:0] representedObject]];
}

- (IBAction)countCodeAction:(id)sender; {
	NSTask *task = [[[NSTask alloc] init] autorelease];
	
	[task setStandardError:[NSPipe pipe]];
	[task setLaunchPath:[[NSBundle mainBundle] pathForResource:@"spasm" ofType:@""]];
	[task setArguments:[NSArray arrayWithObjects:@"-C",@"-V",[@" " stringByAppendingString:[[self currentTextView] selectedString]], nil]];
	
	[task launch];
	[task waitUntilExit];
	
	NSData *outputData = [[[task standardError] fileHandleForReading] availableData];
	NSString *output = [[[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding] autorelease];
	NSArray *parts = [output componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
	
	[WCCodeCountSheetController codeCountSheetWithSize:[[parts firstObject] substringFromIndex:5] minTime:[[parts objectAtIndex:1] substringFromIndex:20] maxTime:[[parts objectAtIndex:2] substringFromIndex:20]];
}
#pragma mark -
#pragma mark *** Private Methods ***
- (NSString *)_projectDataPathFromWrapperURL:(NSURL *)url; {
	return [self _projectDataPathFromWrapperPath:[url path]];
}
- (NSString *)_projectPreferencesPathFromWrapperURL:(NSURL *)url; {
	return [self _projectPreferencesPathFromWrapperPath:[url path]];
}
- (NSString *)_projectDataPathFromWrapperPath:(NSString *)path; {
	return [path stringByAppendingPathComponent:[kWCProjectDataFileName stringByAppendingPathExtension:kWCProjectDataFileExtension]];
}
- (NSString *)_projectPreferencesPathFromWrapperPath:(NSString *)path; {
	return [path stringByAppendingPathComponent:[NSUserName() stringByAppendingPathExtension:kWCProjectPreferencesFileExtension]];
}

- (void)_jumpToBookmark:(WCBookmark *)bookmark; {
	WCTextFile *tf = [bookmark textFile];
	
	if (!tf)
		return;
	
	[self addTabViewItemForTextFile:tf inTabViewContext:self];
	
	WCTextView *tv = [self currentTextView];
	
	if (!tv)
		return;
	
	NSRange range = NSMakeRange([[tv rulerView] lineStartForLineNumber:[bookmark lineNumber]], 0);
	
	[tv setSelectedRange:range];
	[tv scrollRangeToVisible:range];
}

- (void)_showErrorOrWarningForBuildMessage:(WCBuildMessage *)bm; {
	WCTextFile *tf = [bm textFile];
	
	if (!tf)
		return;
	
	[self addTabViewItemForTextFile:tf inTabViewContext:self];
	
	WCTextView *tv = [self currentTextView];
	
	if (!tv)
		return;
	
	NSRange range = NSMakeRange([tf lineStartIndexForLineNumber:[bm lineNumber]], 0);
	
	[tv setSelectedRange:range];
	[tv scrollRangeToVisible:range];
}

- (void)_buildNextStep; {
	if (![[self targets] count] || ![[self steps] count])
		return;
	
	[self removeAllBuildErrorsAndWarnings];
	[self removeAllBuildMessages];

	WCBuildConfiguration *config = [[[self targets] firstObject] activeBuildConfiguration];
	WCBuildStep *step = [[self steps] firstObject];
	
	switch ([step stepType]) {
		case WCBSStepTypeAssembleFile: {
			NSTask *task = [[[NSTask alloc] init] autorelease];
			
			[task setStandardOutput:[NSPipe pipe]];
			[task setLaunchPath:[[NSBundle mainBundle] pathForResource:@"spasm" ofType:@""]];
			[task setCurrentDirectoryPath:[self projectFolderPath]];
			
			NSMutableArray *arguments = [NSMutableArray array];
			
			// add include directories
			[arguments addObjectsFromArray:[config processedIncludeDirectories]];
			// add defines
			[arguments addObjectsFromArray:[config processedDefines]];
			// add the input file name
			[arguments addObject:[[step file] absoluteFilePath]];
			
			NSMutableArray *outputFilePaths = [NSMutableArray array];
			for (NSString *fileName in [step outputFileNames]) {
				[outputFilePaths addObject:[[self buildProductsDirectoryPath] stringByAppendingPathComponent:fileName]];
			}
			[self setBuildProductsOutputFilePaths:outputFilePaths];
			// add the output file name
			[arguments addObject:[[self buildProductsOutputFilePaths] lastObject]];
			
#ifdef WCDEBUG
			NSLogObject(arguments);
#endif
			
			[task setArguments:arguments];
			
			[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_buildTaskDidTerminateNotification:) name:NSTaskDidTerminateNotification object:task];
			
			[self setRootBuildMessage:[WCBuildMessage buildMessageWithType:WCBMTypeBuild text:[NSString stringWithFormat:NSLocalizedString(@"Building target \"%@\" of project \"%@\" with configuration \"%@\"",@"Build results window root string"),[[[self targets] firstObject] name],[self projectName],[config name]] textFile:nil lineNumber:0]];
			
			[self setBuilding:YES];
			[self setBuildTask:task];
			[task launch];
		}
			break;
		case WCBSStepTypeCreateLabelFile: {
			NSTask *task = [[[NSTask alloc] init] autorelease];
			
			[task setStandardOutput:[NSPipe pipe]];
			[task setLaunchPath:[[NSBundle mainBundle] pathForResource:@"spasm" ofType:@""]];
			[task setCurrentDirectoryPath:[self projectFolderPath]];
			
			NSMutableArray *arguments = [NSMutableArray array];
			
			[arguments addObjectsFromArray:[NSArray arrayWithObjects:@"-L",@"-O",nil]];
			[arguments addObjectsFromArray:[config processedIncludeDirectories]];
			[arguments addObjectsFromArray:[config processedDefines]];
			[arguments addObject:[[step file] absoluteFilePath]];
			
			NSMutableArray *outputFilePaths = [NSMutableArray array];
			for (NSString *fileName in [step outputFileNames]) {
				[outputFilePaths addObject:[[self buildProductsDirectoryPath] stringByAppendingPathComponent:fileName]];
			}
			[self setBuildProductsOutputFilePaths:outputFilePaths];
			[arguments addObject:[[self buildProductsOutputFilePaths] firstObject]];
			
#ifdef WCDEBUG
			NSLogObject(arguments);
#endif
			
			[task setArguments:arguments];
			
			[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_buildTaskDidTerminateNotification:) name:NSTaskDidTerminateNotification object:task];
			
			[self setRootBuildMessage:[WCBuildMessage buildMessageWithType:WCBMTypeBuild text:[NSString stringWithFormat:NSLocalizedString(@"Building target \"%@\" of project \"%@\" with configuration \"%@\"",@"Build results window root string"),[[[self targets] firstObject] name],[self projectName],[config name]] textFile:nil lineNumber:0]];
			
			[self setBuilding:YES];
			[self setBuildTask:task];
			[task launch];
		}
			break;
		case WCBSStepTypeRunExecutableFile: {
			// TODO: make this type of step work
		}
			break;
		default:
			break;
	}
}
#pragma mark Project Preferences
- (void)_loadDefaultProjectPreferences; {
	static NSMutableDictionary *defaults = nil;
	if (!defaults) {
		NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"default" ofType:@"wcodeprefs"] options:NSUncachedRead error:NULL];
		NSMutableDictionary *prefs = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListMutableContainers format:NULL errorDescription:NULL];
		
		defaults = [prefs retain];
	}
	
	[_projectPreferences release];
	_projectPreferences = [defaults mutableCopy];
}

- (void)_loadProjectPreferences; {
	NSDictionary *prefs = [self projectPreferences];
	
	// project window frame
	NSString *windowFrame = [prefs objectForKey:kWCProjectPrefsProjectWindowFrameKey];
	if (windowFrame)
		[[self projectWindow] setFrame:NSRectFromString(windowFrame) display:NO];
	else
		[[self projectWindow] center];
	
	// project outline view expanded items
	NSArray *expandedItems = [prefs objectForKey:kWCProjectPrefsProjectOutlineViewExpandedItemIndexPathStringsKey];
	if ([expandedItems count])
		[[self projectOutlineView] expandItems:[expandedItems valueForKey:@"indexPathFromString"]];
	
	// project file detail table view autosave dictionary
	[_fileTableView adjustLayoutForAutosavedDictionary:[prefs objectForKey:kWCProjectPrefsProjectFileTableViewAutosaveDictionaryKey]];
	
	// project errors and warnings table view autosave dictionary
	[_errorsAndWarningsTableView adjustLayoutForAutosavedDictionary:[prefs objectForKey:kWCProjectPrefsProjectErrorsAndWarningsTableViewAutosaveDictionaryKey]];
	
	// bookmarks table view autosave dictionary
	[_bookmarksTableView adjustLayoutForAutosavedDictionary:[prefs objectForKey:kWCProjectPrefsProjectBookmarksTableViewAutosaveDictionaryKey]];
	
	// project symbols table view autosave dictionary
	[_projectSymbolsTableView adjustLayoutForAutosavedDictionary:[prefs objectForKey:kWCProjectPrefsProjectSymbolsTableViewAutosaveDictionaryKey]];
	
	// project file detail view sort descriptors
	NSArray *sortDescriptors = [prefs objectForKey:kWCProjectPrefsProjectFileTableViewSortDescriptorDictionariesKey];
	if ([sortDescriptors count])
		[[self fileDetailTableView] setSortDescriptors:[sortDescriptors sortDescriptorsFromDictionaries]];
	
	// project left vertical split view divider position
	NSNumber *vPosition = [prefs objectForKey:kWCProjectPrefsProjectLeftVerticalSplitViewDividerPositionKey];
	if (vPosition)
		[[self leftVerticalSplitView] setPosition:[vPosition floatValue] ofDividerAtIndex:0];
	
	// project right vertical split view divider position
	NSNumber *vrPosition = [prefs objectForKey:kWCProjectPrefsProjectRightVerticalSplitViewDividerPositionKey];
	if (vrPosition)
		[[self leftVerticalSplitView] setPosition:[vrPosition floatValue] ofDividerAtIndex:1];
	 
	// project horizontal split view divider position
	NSNumber *hPosition = [prefs objectForKey:kWCProjectPrefsProjectHorizontalSplitViewDividerPositionKey];
	if (hPosition)
		[[self horizontalSplitView] setPosition:[hPosition floatValue] ofDividerAtIndex:0];
	
	// project outline view selected row indexes
	NSArray *selectedRowIndexes = [prefs objectForKey:kWCProjectPrefsProjectOutlineViewSelectedRowIndexesKey];
	if ([selectedRowIndexes count])
		[[self projectOutlineView] selectRowIndexes:[selectedRowIndexes indexSetFromArray] byExtendingSelection:NO];
	
	// project open files and selected tab view item
	BOOL animateTabs = [[self psmTabBarControl] automaticallyAnimates];
	[[self psmTabBarControl] setAutomaticallyAnimates:NO];
	NSArray *textFiles = [[self projectGroup] textFiles];
	NSArray *openFilePaths = [prefs objectForKey:kWCProjectPrefsProjectOpenFileNamesKey];
	NSString *selectedFilePath = [prefs objectForKey:kWCProjectPrefsProjectSelectedOpenFileNameKey];
	WCTextFile *fileToSelect = nil;
	for (NSString *fileName in openFilePaths) {
		for (WCTextFile *tf in textFiles) {
			if ([fileName isEqualToString:[tf name]]) {
				[self addTabViewItemForTextFile:tf inTabViewContext:self];
				if ([selectedFilePath isEqualToString:[tf name]])
					fileToSelect = tf;
				
				break;
			}
		}
	}
	[[self psmTabBarControl] setAutomaticallyAnimates:animateTabs];
	
	[self selectTabViewItemWithIdentifier:fileToSelect inTabViewContext:self];
	
	// symbols table view sort descriptors
	NSArray *symbolsSortDescriptors = [prefs objectForKey:kWCProjectPrefsProjectCurrentTextFileSymbolsTableViewSortDescriptorDictionariesKey];
	if ([symbolsSortDescriptors count])
		[[self projectLabelsTableView] setSortDescriptors:[symbolsSortDescriptors sortDescriptorsFromDictionaries]];
	
	// bookmarks table view sort descriptors
	NSArray *bookmarksSortDescriptors = [prefs objectForKey:kWCProjectPrefsProjectBookmarksTableViewSortDescriptorsDictionariesKey];
	if ([bookmarksSortDescriptors count])
		[_bookmarksTableView setSortDescriptors:[bookmarksSortDescriptors sortDescriptorsFromDictionaries]];
	
	// project symbols table view sort descriptors
	NSArray *projectSymbolsSortDescriptors = [prefs objectForKey:kWCProjectPrefsProjectSymbolsTableViewSortDescriptorsDictionariesKey];
	if ([projectSymbolsSortDescriptors count])
		[_projectSymbolsTableView setSortDescriptors:[projectSymbolsSortDescriptors sortDescriptorsFromDictionaries]];
}

- (void)_updateProjectPreferences; {
	NSMutableDictionary *prefs = [self projectPreferences];
	
	// project window frame
	[prefs setObject:NSStringFromRect([[self projectWindow] frame]) forKey:kWCProjectPrefsProjectWindowFrameKey];
	
	// project outline view expanded items
	[prefs setObject:[[[self projectOutlineView] expandedItems] valueForKey:@"stringFromIndexPath"] forKey:kWCProjectPrefsProjectOutlineViewExpandedItemIndexPathStringsKey];
	
	// project file detail view sort descriptors
	NSArray *sortDescriptors = [[self fileDetailTableView] sortDescriptors];
	if ([sortDescriptors count])
		[prefs setObject:[sortDescriptors valueForKey:@"dictionaryFromSortDescriptor"] forKey:kWCProjectPrefsProjectFileTableViewSortDescriptorDictionariesKey];
	
	// project left vertical split view divider position
	CGFloat vPosition = [[[_verticalSplitView subviews] firstObject] frame].size.width;
	[prefs setObject:NSNumberWithFloat(vPosition) forKey:kWCProjectPrefsProjectLeftVerticalSplitViewDividerPositionKey];
	
	// project right vertical split view divider position
	[prefs setObject:NSNumberWithFloat([[[_verticalSplitView subviews] objectAtIndex:1] frame].size.width+vPosition) forKey:kWCProjectPrefsProjectRightVerticalSplitViewDividerPositionKey];
	
	// project horizontal split view divider position
	[prefs setObject:NSNumberWithFloat([[[[self horizontalSplitView] subviews] objectAtIndex:0] frame].size.height) forKey:kWCProjectPrefsProjectHorizontalSplitViewDividerPositionKey];
	
	// project outline view selected row indexes
	[prefs setObject:[[[self projectOutlineView] selectedRowIndexes] arrayWithIndexesFromIndexSet] forKey:kWCProjectPrefsProjectOutlineViewSelectedRowIndexesKey];
	
	// project open file paths
	[prefs setObject:[[[self psmTabBarControl] representedTabViewItems] valueForKeyPath:@"identifier.name"] forKey:kWCProjectPrefsProjectOpenFileNamesKey];
	
	// project selected tab view item path
	if ([self currentTextFile])
		[prefs setObject:[[self currentTextFile] name] forKey:kWCProjectPrefsProjectSelectedOpenFileNameKey];
	
	// symbols table view sort descriptors
	NSArray *symbolsSortDescriptors = [[self projectLabelsTableView] sortDescriptors];
	if ([symbolsSortDescriptors count])
		[prefs setObject:[symbolsSortDescriptors valueForKey:@"dictionaryFromSortDescriptor"] forKey:kWCProjectPrefsProjectCurrentTextFileSymbolsTableViewSortDescriptorDictionariesKey];
	
	// bookmarks table view sort descriptors
	NSArray *bookmarksSortDescriptors = [_bookmarksTableView sortDescriptors];
	if ([bookmarksSortDescriptors count])
		[prefs setObject:[bookmarksSortDescriptors valueForKey:@"dictionaryFromSortDescriptor"] forKey:kWCProjectPrefsProjectBookmarksTableViewSortDescriptorsDictionariesKey];
	
	// project symbols table view sort descriptors
	NSArray *projectSymbolsSortDescriptors = [_projectSymbolsTableView sortDescriptors];
	if ([projectSymbolsSortDescriptors count])
		[prefs setObject:[projectSymbolsSortDescriptors valueForKey:@"dictionaryFromSortDescriptor"] forKey:kWCProjectPrefsProjectSymbolsTableViewSortDescriptorsDictionariesKey];
	
	// file detail table view autosave dictionary
	[prefs setObject:[_fileTableView dictionaryForAutosavingLayout] forKey:kWCProjectPrefsProjectFileTableViewAutosaveDictionaryKey];
	
	// errors and warnings table view autosave dictionary
	[prefs setObject:[_errorsAndWarningsTableView dictionaryForAutosavingLayout] forKey:kWCProjectPrefsProjectErrorsAndWarningsTableViewAutosaveDictionaryKey];
	
	// bookmarks table view autosave dictionary
	[prefs setObject:[_bookmarksTableView dictionaryForAutosavingLayout] forKey:kWCProjectPrefsProjectBookmarksTableViewAutosaveDictionaryKey];
	
	// project symbols table view autosave dictionary
	[prefs setObject:[_projectSymbolsTableView dictionaryForAutosavingLayout] forKey:kWCProjectPrefsProjectSymbolsTableViewAutosaveDictionaryKey];
	
	// find in project window settings
	if (_findInProjectWindowController) {
		// ignore case checkbox
		[prefs setObject:NSNumberWithBool([_findInProjectWindowController ignoreCase]) forKey:kWCProjectPrefsFindInProjectIgnoreCaseKey];
		
		// use regular expressions
		[prefs setObject:NSNumberWithBool([_findInProjectWindowController useRegularExpressions]) forKey:kWCProjectPrefsFindInProjectUseRegularExpressionsKey];
		
		// window frame
		[prefs setObject:NSStringFromRect([[_findInProjectWindowController window] frame]) forKey:kWCProjectPrefsFindInProjectWindowFrameKey];
		
		// split view divider position
		[prefs setObject:NSNumberWithFloat([[[[_findInProjectWindowController splitView] subviews] firstObject] frame].size.height) forKey:kWCProjectPrefsFindInProjectSplitViewDividerPositionKey];
		
		// find combo box recents
		[prefs setObject:[_findInProjectWindowController findRecents] forKey:kWCProjectPrefsFindInProjectFindRecentsKey];
		
		// replace combo box recents
		[prefs setObject:[_findInProjectWindowController replaceRecents] forKey:kWCProjectPrefsFindInProjectReplaceRecentsKey];
		
		// search scope identifier
		[prefs setObject:[[[[_findInProjectWindowController mgScopeBar] selectedItems] firstObject] firstObject] forKey:kWCProjectPrefsFindInProjectScopeIdentifierKey];
	}
	
	if (_buildResultsWindowController) {
		// window frame
		[prefs setObject:NSStringFromRect([[_buildResultsWindowController window] frame]) forKey:kWCProjectPrefsBuildResultsWindowFrameKey];
		
		// split view divider position
		[prefs setObject:NSNumberWithFloat([[[[_buildResultsWindowController splitView] subviews] firstObject] frame].size.height) forKey:kWCProjectPrefsBuildResultsSplitViewDividerPositionKey];
	}
}

#pragma mark Callbacks
- (void)_deleteFilesAlertDidEnd:(NSAlert *)alert code:(NSInteger)code info:(void *)info {
	if (code == NSAlertOtherReturn)
		return;
	else if (code == NSAlertAlternateReturn)
		[self deleteObjects:[self selectedRepresentedObjects] moveFilesToTrash:YES];
	else
		[self deleteObjects:[self selectedRepresentedObjects] moveFilesToTrash:NO];
}

- (void)_deleteAlertDidEnd:(NSAlert *)alert code:(NSInteger)code info:(void *)info {
	if (code != NSAlertDefaultReturn)
		return;
	
	[self deleteObjects:[self selectedRepresentedObjects] moveFilesToTrash:NO];
}
#pragma mark Notifications
- (void)_buildTaskDidTerminateNotification:(NSNotification *)note {
#ifdef WCDEBUG
	NSLog(@"build task finished");
#endif
	
	// unregister for the notification for the task that just completed
	[[NSNotificationCenter defaultCenter] removeObserver:self name:NSTaskDidTerminateNotification object:[note object]];
	
	// grab all the output from stdout
	NSData *data = [[[[note object] standardOutput] fileHandleForReading] availableData];
	// create a string with it
	NSString *output = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
	// split the string into lines, each line is a separate message from spasm
	NSArray *messages = [output componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
	NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet];
	
	for (NSString *message in messages) {
		// split the message into fileName, lineNumber, type, reason
		NSArray *messageParts = [message componentsSeparatedByString:@":"];
		
		// if the message doesn't have four parts it's not an error or warning
		if ([messageParts count] != 4)
			continue;
		
		NSString *fileName = [[[messageParts objectAtIndex:0] lastPathComponent] stringByTrimmingCharactersInSet:whitespace];
		NSUInteger lineNumber = [[messageParts objectAtIndex:1] integerValue];
		NSString *errorOrWarning = [[messageParts objectAtIndex:2] stringByTrimmingCharactersInSet:whitespace];
		NSString *reason = [[messageParts objectAtIndex:3] stringByTrimmingCharactersInSet:whitespace];
		WCBuildMessage *parentBuildMessage = nil;
		WCBuildMessage *rootParentBuildMessage = nil;
		
		// start by searching the child nodes of the errors and warnings group
		for (WCBuildMessage *bm in [[[self errorsAndWarningsGroup] childNodes] reverseObjectEnumerator]) {
			if ([fileName isEqualToString:[[bm textFile] name]]) {
				parentBuildMessage = bm;
				break;
			}
		}
		
		for (WCBuildMessage *bm in [[[self rootBuildMessage] childNodes] reverseObjectEnumerator]) {
			if ([fileName isEqualToString:[[bm textFile] name]]) {
				rootParentBuildMessage = bm;
				break;
			}
		}
		
		// if it wasn't a recent file then we have to search through all of them
		if (!parentBuildMessage) {
			for (WCTextFile *tf in [self textFiles]) {
				if ([fileName isEqualToString:[tf name]]) {
					parentBuildMessage = [WCBuildMessage buildMessageWithType:WCBMTypeFile text:fileName textFile:tf lineNumber:0];
					[[[self errorsAndWarningsGroup] mutableChildNodes] addObject:parentBuildMessage];
				}
			}
		}
		if (!rootParentBuildMessage) {
			for (WCTextFile *tf in [self textFiles]) {
				if ([fileName isEqualToString:[tf name]]) {
					rootParentBuildMessage = [WCBuildMessage buildMessageWithType:WCBMTypeFileExpanded text:fileName textFile:tf lineNumber:0];
					[[[self rootBuildMessage] mutableChildNodes] addObject:rootParentBuildMessage];
				}
			}
		}
		
		WCBuildMessage *errorOrWarningMessage = nil;
		// we have an error
		if ([errorOrWarning isEqualToString:@"error"]) {
			errorOrWarningMessage = [WCBuildMessage buildMessageWithType:WCBMTypeError text:reason textFile:[parentBuildMessage textFile] lineNumber:lineNumber];
			
			[[parentBuildMessage mutableChildNodes] addObject:errorOrWarningMessage];
			[[rootParentBuildMessage mutableChildNodes] addObject:[[errorOrWarningMessage copy] autorelease]];
		}
		// otherwise it's a warning
		else {
			errorOrWarningMessage = [WCBuildMessage buildMessageWithType:WCBMTypeWarning text:reason textFile:[parentBuildMessage textFile] lineNumber:lineNumber];
			[[parentBuildMessage mutableChildNodes] addObject:errorOrWarningMessage];
			[[rootParentBuildMessage mutableChildNodes] addObject:[[errorOrWarningMessage copy] autorelease]];
		}
	}
	
	// get the total number of errors and warnings for this build step
	NSUInteger numberOfErrors = [[self errorsAndWarningsGroup] numberOfErrors];
	NSUInteger numberOfWarnings = [[self errorsAndWarningsGroup] numberOfWarnings];
	NSString *errorString = (numberOfErrors == 1)?NSLocalizedString(@"error",@"error"):NSLocalizedString(@"errors",@"errors");
	NSString *warningString = (numberOfWarnings == 1)?NSLocalizedString(@"warning",@"warning"):NSLocalizedString(@"warnings",@"warnings");
	NSString *statusString = nil;
	NSArray *buildStatusImagesAndStrings = nil;
	WCBuildMessage *finalBuildMessage = nil;
	
	// update our various status messages
	if (numberOfErrors && numberOfWarnings) {
		statusString = [NSString stringWithFormat:NSLocalizedString(@"Build failed (%u %@, %u %@)",@"build failed with errors and warnings status string"), numberOfErrors, errorString, numberOfWarnings, warningString, nil];
		buildStatusImagesAndStrings = [NSArray arrayWithObjects:NSImageNamed(@"Build14x14"), NSLocalizedString(@"Failed",@"Failed"), NSImageNamed(@"Error12x12"), [NSString stringWithFormat:@"%u", numberOfErrors], NSImageNamed(@"Warning12x12"), [NSString stringWithFormat:@"%u", numberOfWarnings], nil];
		finalBuildMessage = [WCBuildMessage buildMessageWithType:WCBMTypeFailureErrors text:statusString textFile:nil lineNumber:0];
		
		[[[self rootBuildMessage] mutableChildNodes] addObject:finalBuildMessage];		
	}
	else if (numberOfErrors) {
		statusString = [NSString stringWithFormat:NSLocalizedString(@"Build failed (%u %@)",@"build failed with errors status string"), numberOfErrors, errorString, nil];
		buildStatusImagesAndStrings = [NSArray arrayWithObjects:NSImageNamed(@"Build14x14"), NSLocalizedString(@"Failed",@"Failed"), NSImageNamed(@"Error12x12"), [NSString stringWithFormat:@"%u", numberOfErrors], nil];
		finalBuildMessage = [WCBuildMessage buildMessageWithType:WCBMTypeFailureErrors text:statusString textFile:nil lineNumber:0];
		
		[[[self rootBuildMessage] mutableChildNodes] addObject:finalBuildMessage];
	}
	else if (numberOfWarnings) {
		statusString = [NSString stringWithFormat:NSLocalizedString(@"Build succeeded (%u %@)",@"build succeeded with warnings status string"), numberOfWarnings, warningString, nil];
		buildStatusImagesAndStrings = [NSArray arrayWithObjects:NSImageNamed(@"Build14x14"), NSLocalizedString(@"Succeeded",@"Succeeded"), NSImageNamed(@"Warning12x12"), [NSString stringWithFormat:@"%u", numberOfWarnings], nil];
		finalBuildMessage = [WCBuildMessage buildMessageWithType:WCBMTypeSuccessWarnings text:statusString textFile:nil lineNumber:0];
		
		[[[self rootBuildMessage] mutableChildNodes] addObject:finalBuildMessage];
	}
	else {
		statusString = NSLocalizedString(@"Build succeeded",@"build succeeded status string");
		buildStatusImagesAndStrings = [NSArray arrayWithObjects:NSImageNamed(@"Build14x14"), NSLocalizedString(@"Succeeded",@"Succeeded"), nil];
		finalBuildMessage = [WCBuildMessage buildMessageWithType:WCBMTypeBuildSuccess text:NSLocalizedString(@"Build succeeded",@"Build succeeded") textFile:nil lineNumber:0];
		
		[self setRootBuildMessage:finalBuildMessage];
	}
	[self setProjectInfoString:statusString];
	[self setBuildStatusImagesAndStrings:buildStatusImagesAndStrings];
	
	// update the dock badges
	[[WCDockBadgeView sharedInstance] updateErrorCount:numberOfErrors warningCount:numberOfWarnings];
	
	BOOL shouldBuildNextStep = NO;
	if (!numberOfErrors && !numberOfWarnings) {
		// build the next step of the current target
		[[self steps] removeFirstObject];
		
		if (![[self steps] count]) {
			[[self targets] removeFirstObject];
			
			if ([[self targets] count])
				shouldBuildNextStep = YES;
		}
		else {
			shouldBuildNextStep = YES;
		}
	}
	
	if (!numberOfErrors && !numberOfWarnings) {
		for (NSString *filePath in [self buildProductsOutputFilePaths]) {
			[[[self buildProductsGroup] mutableChildNodes] addObject:[WCBuildProduct buildProductWithFilePath:filePath inProject:self]];
		}
	}
	
	if (shouldBuildNextStep) {
		[self _buildNextStep];
	}
	else {
		[self updateSwapView];
		[self setBuilding:NO];
		[[[self buildResultsWindowControllerDontCreate] outlineView] expandItem:[[[self buildResultsWindowControllerDontCreate] outlineView] itemAtRow:0] expandChildren:YES];
	}
}

- (void)_applicationDidBecomeActiveNotification:(NSNotification *)note {
	[_projectOutlineView setNeedsDisplay:YES];
	[_fileTableView setNeedsDisplay:YES];
	
	if (![NSApp modalWindow]) {
		for (WCTextFile *tf in [self openTextFiles]) {
			[self setWaitingForModalAlertReply:YES];
			[tf validateTextFile];
			[self setWaitingForModalAlertReply:NO];
		}
	}
}

- (void)_activeBuildTargetDidChangeNotification:(NSNotification *)note {
	// build the union of all the build target row rects in the project
	NSRect unionRect = NSZeroRect;
	
	for (WCBuildTarget *target in [[note object] childNodes])
		unionRect = NSUnionRect(unionRect, [_projectOutlineView rectOfRow:[_projectOutlineView rowForItem:[[self projectTreeController] treeNodeForRepresentedObject:target]]]);
	
	[_projectOutlineView setNeedsDisplayInRect:unionRect];
}

- (void)_fileChangeCountDidChangeNotification:(NSNotification *)note {
	if ([[self openTextFiles] containsObject:[note object]])
		[_projectOutlineView setNeedsDisplayInRect:[_projectOutlineView rectOfRow:[_projectOutlineView rowForItem:[[_projectOutlineView dataSource] treeNodeForRepresentedObject:[note object]]]]];
}
#pragma mark IBActions
- (IBAction)_projectOutlineViewDoubleAction:(id)sender {
	id item = [[[sender dataSource] selectedNodes] firstObject];
	if ([[item representedObject] isKindOfClass:[WCBuildTarget class]]) {
		[self showGetInfoWindowForItem:[item representedObject]]; 
	}
	else if ([[item representedObject] isKindOfClass:[WCBuildStep class]]) {
		WCBuildTargetGetInfoWindowController *wc = [self showGetInfoWindowForItem:[[item representedObject] buildTarget]];
		[[wc tabView] selectTabViewItemWithIdentifier:kWCBuildTargetGetInfoWindowBuildStepsTabIdentifier];
	}
	else if (![item isLeaf]) {
		if ([sender isItemExpanded:item])
			[sender collapseItem:item];
		else
			[sender expandItem:item];
	}
	else {
		[self openWithFinderAction:nil];
	}
}

- (IBAction)_fileTableViewDoubleAction:(id)sender; {
	[self openWithFinderAction:nil];
}

- (IBAction)_errorsAndWarningsTableViewDoubleAction:(id)sender; {
	NSArray *output = [[_errorsAndWarningsTableView dataSource] selectedObjects];
	if (![output count])
		return;
	
	[self _showErrorOrWarningForBuildMessage:[output firstObject]];
}

- (IBAction)_projectSymbolsTableViewDoubleAction:(id)sender; {
	WCSymbol *symbol = [[[sender dataSource] selectedObjects] firstObject];
	
	[self addTabViewItemForTextFile:[symbol textFile] inTabViewContext:self];
	
	WCTextView *tv = [self currentTextView];
	NSString *string = [tv string];
	NSRange range = [string lineRangeForRange:NSMakeRange([[symbol textFile] lineStartIndexForLineNumber:[symbol lineNumber]], 0)];
	
	range = [string rangeOfString:[symbol name] options:NSLiteralSearch range:range];
	
	[tv setSelectedRange:range];
	[tv scrollRangeToVisible:[tv selectedRange]];
	[tv showFindIndicatorForRange:[tv selectedRange]];
}

- (IBAction)_currentTextFileSymbolsTableViewDoubleAction:(id)sender; {
	WCSymbol *symbol = [[[sender dataSource] selectedObjects] firstObject];
	
	WCTextView *tv = [self currentTextView];
	NSString *string = [tv string];
	NSRange range = [string lineRangeForRange:NSMakeRange([[symbol textFile] lineStartIndexForLineNumber:[symbol lineNumber]], 0)];
	
	range = [string rangeOfString:[symbol name] options:NSLiteralSearch range:range];
	
	[tv setSelectedRange:range];
	[tv scrollRangeToVisible:[tv selectedRange]];
	[tv showFindIndicatorForRange:[tv selectedRange]];
}

@end
